GitHub Action
commited on
Commit
·
1e1ee41
1
Parent(s):
e6c82a2
Sync from GitHub with Git LFS
Browse files- agents/bootstrap.txt +3 -4
- agents/examples/bootstrap.txt +3 -4
- agents/init.py +8 -8
- agents/peer_sync.py +170 -96
- agents/requirements.txt +2 -1
- agents/tools/storage.py +19 -4
agents/bootstrap.txt
CHANGED
@@ -1,4 +1,3 @@
|
|
1 |
-
DID: "did:hmp:
|
2 |
-
DID: "did:hmp:
|
3 |
-
DID: "did:hmp:
|
4 |
-
DID: "did:hmp:3a30112e-6b14-4161-8ce9-189a6f4bdd1e"; NAME: "Agent_120"; KEY: "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAPIYEVWz9lBfxBI1MCHMoyVqAJpWH5wwLjAAFKV7Q6UM=\n-----END PUBLIC KEY-----\n"; ADDRESS: [{"addr": "tcp://95.72.96.120:4000", "nonce": 48308, "pow_hash": "0000bbf1c4880c752362fe6100d1eddb98530e9b36f7dda366dfbd3e8860a680", "difficulty": 4, "datetime": "2025-08-28T15:50:17.822119+00:00"}, {"addr": "udp://95.72.96.120:4000", "nonce": 277204, "pow_hash": "0000e71c10d990fb4af04a3a171f9735291ab21a8807acf9284fc39817e028a3", "difficulty": 4, "datetime": "2025-08-28T15:50:17.935199+00:00"}, {"addr": "tcp://195.172.229.120:4000", "nonce": 74353, "pow_hash": "0000c16fcdbbc456b93680fdf48e3a844a477d038c362dd01d70219d566defb0", "difficulty": 4, "datetime": "2025-08-28T15:50:18.661732+00:00"}, {"addr": "udp://195.172.229.120:4000", "nonce": 9937, "pow_hash": "000019a037ff2c15c8b88e45d63152dee141a779628370692973626bf0c343f7", "difficulty": 4, "datetime": "2025-08-28T15:50:18.849814+00:00"}];
|
|
|
1 |
+
DID: "did:hmp:300d25eb-25e2-4f8a-a1a9-818f09f67be4"; NAME: "Agent_203"; KEY: "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAAvaR44j54884fTvTX4qQ1o7bvpaT8ZnP/+FHmhl4xp4=\n-----END PUBLIC KEY-----\n"; ADDRESS: [{"addr": "tcp://97.203.96.27:4010", "nonce": 7364, "pow_hash": "00005fa2053a7dbc7e06269d427540668b7832982be8504189ae60822f17b613", "difficulty": 4, "datetime": "2025-08-28T21:01:16+00:00"}, {"addr": "udp://97.203.96.27:4010", "nonce": 46359, "pow_hash": "00008c063b6964de0bc509f412fc4bb13f6b61a55e96ed7f16ff5f1d2b8615b8", "difficulty": 4, "datetime": "2025-08-28T21:01:16+00:00"}];
|
2 |
+
DID: "did:hmp:424e79ff-6d44-4b43-9c04-883f76bce800"; NAME: "Agent_207"; KEY: "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAQ4p/UgxONBwF1Zo7dj0IqseesMpydBmqrWCZZrNnqAw=\n-----END PUBLIC KEY-----\n"; ADDRESS: [{"addr": "tcp://97.207.96.27:4010", "nonce": 90667, "pow_hash": "000078c10f5bf921115e57f73270ce36135ed2d92584bd7477afcd6951db55da", "difficulty": 4, "datetime": "2025-08-28T21:04:08+00:00"}, {"addr": "udp://97.207.96.27:4010", "nonce": 9245, "pow_hash": "000050bb4afd13fec665d289e52df1a71001f47e9cda29665d43a60ac260bb9d", "difficulty": 4, "datetime": "2025-08-28T21:04:08+00:00"}];
|
3 |
+
DID: "did:hmp:5f8bd63d-f31e-46aa-b95a-f95775945549"; NAME: "Agent_209"; KEY: "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA5IZRyvQZDqEo+/W4sHRVyoSh1yRElqmIYKAyO54y8sY=\n-----END PUBLIC KEY-----\n"; ADDRESS: [{"addr": "tcp://97.209.96.27:4010", "nonce": 20846, "pow_hash": "0000736894e8b9a06edaba964cabc5341f18102c4f20248aa27bb13fb4d13d71", "difficulty": 4, "datetime": "2025-08-28T21:07:58+00:00"}, {"addr": "udp://97.209.96.27:4010", "nonce": 117913, "pow_hash": "0000f5b2b30244f7851ccf9473fa89583034201e4d9b26b3261cb6ea70034051", "difficulty": 4, "datetime": "2025-08-28T21:07:58+00:00"}];
|
|
agents/examples/bootstrap.txt
CHANGED
@@ -1,4 +1,3 @@
|
|
1 |
-
DID: "did:hmp:
|
2 |
-
DID: "did:hmp:
|
3 |
-
DID: "did:hmp:
|
4 |
-
DID: "did:hmp:3a30112e-6b14-4161-8ce9-189a6f4bdd1e"; NAME: "Agent_120"; KEY: "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAPIYEVWz9lBfxBI1MCHMoyVqAJpWH5wwLjAAFKV7Q6UM=\n-----END PUBLIC KEY-----\n"; ADDRESS: [{"addr": "tcp://95.72.96.120:4000", "nonce": 48308, "pow_hash": "0000bbf1c4880c752362fe6100d1eddb98530e9b36f7dda366dfbd3e8860a680", "difficulty": 4, "datetime": "2025-08-28T15:50:17.822119+00:00"}, {"addr": "udp://95.72.96.120:4000", "nonce": 277204, "pow_hash": "0000e71c10d990fb4af04a3a171f9735291ab21a8807acf9284fc39817e028a3", "difficulty": 4, "datetime": "2025-08-28T15:50:17.935199+00:00"}, {"addr": "tcp://195.172.229.120:4000", "nonce": 74353, "pow_hash": "0000c16fcdbbc456b93680fdf48e3a844a477d038c362dd01d70219d566defb0", "difficulty": 4, "datetime": "2025-08-28T15:50:18.661732+00:00"}, {"addr": "udp://195.172.229.120:4000", "nonce": 9937, "pow_hash": "000019a037ff2c15c8b88e45d63152dee141a779628370692973626bf0c343f7", "difficulty": 4, "datetime": "2025-08-28T15:50:18.849814+00:00"}];
|
|
|
1 |
+
DID: "did:hmp:300d25eb-25e2-4f8a-a1a9-818f09f67be4"; NAME: "Agent_203"; KEY: "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAAvaR44j54884fTvTX4qQ1o7bvpaT8ZnP/+FHmhl4xp4=\n-----END PUBLIC KEY-----\n"; ADDRESS: [{"addr": "tcp://97.203.96.27:4010", "nonce": 7364, "pow_hash": "00005fa2053a7dbc7e06269d427540668b7832982be8504189ae60822f17b613", "difficulty": 4, "datetime": "2025-08-28T21:01:16+00:00"}, {"addr": "udp://97.203.96.27:4010", "nonce": 46359, "pow_hash": "00008c063b6964de0bc509f412fc4bb13f6b61a55e96ed7f16ff5f1d2b8615b8", "difficulty": 4, "datetime": "2025-08-28T21:01:16+00:00"}];
|
2 |
+
DID: "did:hmp:424e79ff-6d44-4b43-9c04-883f76bce800"; NAME: "Agent_207"; KEY: "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAQ4p/UgxONBwF1Zo7dj0IqseesMpydBmqrWCZZrNnqAw=\n-----END PUBLIC KEY-----\n"; ADDRESS: [{"addr": "tcp://97.207.96.27:4010", "nonce": 90667, "pow_hash": "000078c10f5bf921115e57f73270ce36135ed2d92584bd7477afcd6951db55da", "difficulty": 4, "datetime": "2025-08-28T21:04:08+00:00"}, {"addr": "udp://97.207.96.27:4010", "nonce": 9245, "pow_hash": "000050bb4afd13fec665d289e52df1a71001f47e9cda29665d43a60ac260bb9d", "difficulty": 4, "datetime": "2025-08-28T21:04:08+00:00"}];
|
3 |
+
DID: "did:hmp:5f8bd63d-f31e-46aa-b95a-f95775945549"; NAME: "Agent_209"; KEY: "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA5IZRyvQZDqEo+/W4sHRVyoSh1yRElqmIYKAyO54y8sY=\n-----END PUBLIC KEY-----\n"; ADDRESS: [{"addr": "tcp://97.209.96.27:4010", "nonce": 20846, "pow_hash": "0000736894e8b9a06edaba964cabc5341f18102c4f20248aa27bb13fb4d13d71", "difficulty": 4, "datetime": "2025-08-28T21:07:58+00:00"}, {"addr": "udp://97.209.96.27:4010", "nonce": 117913, "pow_hash": "0000f5b2b30244f7851ccf9473fa89583034201e4d9b26b3261cb6ea70034051", "difficulty": 4, "datetime": "2025-08-28T21:07:58+00:00"}];
|
|
agents/init.py
CHANGED
@@ -55,8 +55,8 @@ def init_identity(storage, config):
|
|
55 |
"pubkey": pubkey,
|
56 |
"privkey": privkey,
|
57 |
"metadata": json.dumps({"role": config.get("agent_role", "core")}),
|
58 |
-
"created_at": datetime.now(UTC).isoformat(),
|
59 |
-
"updated_at": datetime.now(UTC).isoformat()
|
60 |
}
|
61 |
storage.add_identity(identity)
|
62 |
|
@@ -109,7 +109,7 @@ def init_llm_backends(storage, config):
|
|
109 |
"name": backend["name"],
|
110 |
"endpoint": desc,
|
111 |
"metadata": json.dumps(backend),
|
112 |
-
"created_at": datetime.now(UTC).isoformat()
|
113 |
}
|
114 |
storage.add_llm(llm)
|
115 |
print(f"[+] Зарегистрирован LLM: {backend['name']}")
|
@@ -140,7 +140,7 @@ def update_pow_for_addresses(storage, difficulty=4):
|
|
140 |
"nonce": 108834,
|
141 |
"pow_hash": "0000665ea1440781356d7a9b899fc03a01a4f8342d3cfa3d75bb2619b66b4cfb",
|
142 |
"difficulty": 4,
|
143 |
-
"datetime": "2025-08-28T11:00:00+
|
144 |
}
|
145 |
"""
|
146 |
raw_id = storage.get_config_value("agent_id")
|
@@ -171,7 +171,7 @@ def update_pow_for_addresses(storage, difficulty=4):
|
|
171 |
|
172 |
enriched = []
|
173 |
for addr in addresses:
|
174 |
-
dt_now = datetime.now(UTC).isoformat()
|
175 |
|
176 |
if isinstance(addr, dict) and "addr" in addr and "pow_hash" in addr:
|
177 |
# уже в новом формате → оставляем как есть
|
@@ -180,7 +180,7 @@ def update_pow_for_addresses(storage, difficulty=4):
|
|
180 |
|
181 |
# строка → нужно сгенерировать PoW
|
182 |
addr_str = addr if isinstance(addr, str) else addr.get("address")
|
183 |
-
dt_now = datetime.now(UTC).isoformat()
|
184 |
if addr_str:
|
185 |
nonce, hash_value, dt_now = storage.generate_pow(
|
186 |
peer_id=agent_id,
|
@@ -227,7 +227,7 @@ def init_prompts_and_ethics():
|
|
227 |
ON CONFLICT(id) DO UPDATE SET
|
228 |
content=excluded.content,
|
229 |
updated_at=excluded.updated_at
|
230 |
-
""", (pid, fname, ptype, "1.0", "local", content, datetime.now(UTC).isoformat()))
|
231 |
print(f"[+] Загружен промпт: {fname} ({ptype})")
|
232 |
|
233 |
# Загружаем ethics.yml
|
@@ -273,7 +273,7 @@ def init_prompts_and_ethics():
|
|
273 |
json.dumps(ethics_data.get("evaluation"), ensure_ascii=False),
|
274 |
json.dumps(ethics_data.get("violation_policy"), ensure_ascii=False),
|
275 |
json.dumps(ethics_data.get("audit"), ensure_ascii=False),
|
276 |
-
datetime.now(UTC).isoformat()
|
277 |
))
|
278 |
print(f"[+] Загружена этическая политика: {eid}")
|
279 |
else:
|
|
|
55 |
"pubkey": pubkey,
|
56 |
"privkey": privkey,
|
57 |
"metadata": json.dumps({"role": config.get("agent_role", "core")}),
|
58 |
+
"created_at": datetime.now(UTC).replace(microsecond=0).isoformat(),
|
59 |
+
"updated_at": datetime.now(UTC).replace(microsecond=0).isoformat()
|
60 |
}
|
61 |
storage.add_identity(identity)
|
62 |
|
|
|
109 |
"name": backend["name"],
|
110 |
"endpoint": desc,
|
111 |
"metadata": json.dumps(backend),
|
112 |
+
"created_at": datetime.now(UTC).replace(microsecond=0).isoformat()
|
113 |
}
|
114 |
storage.add_llm(llm)
|
115 |
print(f"[+] Зарегистрирован LLM: {backend['name']}")
|
|
|
140 |
"nonce": 108834,
|
141 |
"pow_hash": "0000665ea1440781356d7a9b899fc03a01a4f8342d3cfa3d75bb2619b66b4cfb",
|
142 |
"difficulty": 4,
|
143 |
+
"datetime": "2025-08-28T11:00:00+00:00"
|
144 |
}
|
145 |
"""
|
146 |
raw_id = storage.get_config_value("agent_id")
|
|
|
171 |
|
172 |
enriched = []
|
173 |
for addr in addresses:
|
174 |
+
dt_now = datetime.now(UTC).replace(microsecond=0).isoformat()
|
175 |
|
176 |
if isinstance(addr, dict) and "addr" in addr and "pow_hash" in addr:
|
177 |
# уже в новом формате → оставляем как есть
|
|
|
180 |
|
181 |
# строка → нужно сгенерировать PoW
|
182 |
addr_str = addr if isinstance(addr, str) else addr.get("address")
|
183 |
+
dt_now = datetime.now(UTC).replace(microsecond=0).isoformat()
|
184 |
if addr_str:
|
185 |
nonce, hash_value, dt_now = storage.generate_pow(
|
186 |
peer_id=agent_id,
|
|
|
227 |
ON CONFLICT(id) DO UPDATE SET
|
228 |
content=excluded.content,
|
229 |
updated_at=excluded.updated_at
|
230 |
+
""", (pid, fname, ptype, "1.0", "local", content, datetime.now(UTC).replace(microsecond=0).isoformat()))
|
231 |
print(f"[+] Загружен промпт: {fname} ({ptype})")
|
232 |
|
233 |
# Загружаем ethics.yml
|
|
|
273 |
json.dumps(ethics_data.get("evaluation"), ensure_ascii=False),
|
274 |
json.dumps(ethics_data.get("violation_policy"), ensure_ascii=False),
|
275 |
json.dumps(ethics_data.get("audit"), ensure_ascii=False),
|
276 |
+
datetime.now(UTC).replace(microsecond=0).isoformat()
|
277 |
))
|
278 |
print(f"[+] Загружена этическая политика: {eid}")
|
279 |
else:
|
agents/peer_sync.py
CHANGED
@@ -8,6 +8,9 @@ import select
|
|
8 |
import netifaces
|
9 |
import re
|
10 |
import ipaddress
|
|
|
|
|
|
|
11 |
|
12 |
from datetime import datetime, timezone as UTC
|
13 |
from tools.storage import Storage
|
@@ -134,67 +137,76 @@ def load_bootstrap_peers(filename="bootstrap.txt"):
|
|
134 |
# UDP Discovery
|
135 |
# ---------------------------
|
136 |
def udp_discovery():
|
|
|
137 |
DISCOVERY_INTERVAL = 30
|
138 |
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
|
|
148 |
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
pubkey = entry.get("pubkey") if isinstance(entry, dict) else None
|
154 |
-
if not addr_str:
|
155 |
-
continue
|
156 |
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
161 |
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
listen_sockets.append(sock6)
|
180 |
-
print(f"[UDP Discovery] Listening IPv6 on [::]:{port}")
|
181 |
-
except Exception as e:
|
182 |
-
print(f"[UDP Discovery] IPv6 bind failed on port {port}: {e}")
|
183 |
|
184 |
-
|
|
|
|
|
185 |
if listen_sockets:
|
186 |
rlist, _, _ = select.select(listen_sockets, [], [], 0.5)
|
187 |
for sock in rlist:
|
188 |
try:
|
189 |
data, addr = sock.recvfrom(2048)
|
190 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
191 |
peer_id = msg.get("id")
|
192 |
if peer_id == my_id:
|
193 |
continue
|
194 |
name = msg.get("name", "unknown")
|
195 |
addresses = msg.get("addresses", [])
|
196 |
|
197 |
-
# Фильтруем адреса по PoW и datetime
|
198 |
valid_addresses = []
|
199 |
for a in addresses:
|
200 |
addr_str = a.get("addr")
|
@@ -204,20 +216,28 @@ def udp_discovery():
|
|
204 |
dt = a.get("datetime")
|
205 |
pubkey = a.get("pubkey")
|
206 |
|
207 |
-
if not addr_str:
|
208 |
continue
|
209 |
|
210 |
# Проверка PoW
|
211 |
-
if nonce is not None and pow_hash and difficulty is not None
|
212 |
-
|
|
|
|
|
213 |
continue
|
214 |
|
215 |
-
#
|
216 |
existing = storage.get_peer_address(peer_id, addr_str)
|
217 |
-
|
218 |
-
existing_dt = existing.get("datetime")
|
219 |
-
|
220 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
221 |
|
222 |
valid_addresses.append(a)
|
223 |
|
@@ -229,27 +249,31 @@ def udp_discovery():
|
|
229 |
source="discovery",
|
230 |
status="online"
|
231 |
)
|
232 |
-
print(f"[UDP Discovery] peer
|
233 |
|
234 |
except Exception as e:
|
235 |
print(f"[UDP Discovery] receive error: {e}")
|
236 |
|
237 |
-
# Отправка broadcast/multicast
|
|
|
238 |
valid_local_addresses = []
|
|
|
239 |
for a in local_addresses:
|
240 |
addr_str = a.get("addr") if isinstance(a, dict) else a
|
241 |
nonce = a.get("nonce")
|
242 |
pow_hash = a.get("pow_hash")
|
243 |
difficulty = a.get("difficulty")
|
244 |
dt = a.get("datetime")
|
245 |
-
pubkey = a.get("pubkey") if isinstance(a, dict) else
|
246 |
|
247 |
-
if not addr_str
|
248 |
continue
|
249 |
|
250 |
-
# Проверка PoW
|
251 |
if nonce is not None and pow_hash and difficulty is not None:
|
252 |
-
|
|
|
|
|
253 |
continue
|
254 |
|
255 |
valid_local_addresses.append(a)
|
@@ -260,6 +284,8 @@ def udp_discovery():
|
|
260 |
"addresses": valid_local_addresses
|
261 |
}).encode("utf-8")
|
262 |
|
|
|
|
|
263 |
for entry in valid_local_addresses:
|
264 |
addr_str = entry.get("addr")
|
265 |
proto, hostport = addr_str.split("://", 1)
|
@@ -276,10 +302,11 @@ def udp_discovery():
|
|
276 |
try:
|
277 |
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
278 |
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
|
|
279 |
sock.sendto(msg_data, (a["broadcast"], port))
|
280 |
sock.close()
|
281 |
-
except Exception:
|
282 |
-
|
283 |
|
284 |
# IPv6 multicast ff02::1
|
285 |
else:
|
@@ -294,10 +321,11 @@ def udp_discovery():
|
|
294 |
sock6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
295 |
sock6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, socket.if_nametoindex(iface))
|
296 |
sock6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, 1)
|
|
|
297 |
sock6.sendto(msg_data, (multicast_addr, port))
|
298 |
sock6.close()
|
299 |
-
except Exception:
|
300 |
-
|
301 |
|
302 |
time.sleep(DISCOVERY_INTERVAL)
|
303 |
|
@@ -309,6 +337,7 @@ def udp_discovery():
|
|
309 |
# TCP Peer Exchange (исходящие)
|
310 |
# ---------------------------
|
311 |
def tcp_peer_exchange():
|
|
|
312 |
PEER_EXCHANGE_INTERVAL = 20 # секунды для отладки
|
313 |
|
314 |
while True:
|
@@ -336,23 +365,29 @@ def tcp_peer_exchange():
|
|
336 |
dt = addr_entry.get("datetime")
|
337 |
pubkey = addr_entry.get("pubkey")
|
338 |
|
339 |
-
# Нормализация адреса
|
340 |
norm = storage.normalize_address(addr_str)
|
341 |
if not norm:
|
342 |
continue
|
343 |
|
344 |
# Проверка PoW
|
345 |
if nonce is not None and pow_hash and difficulty is not None and pubkey:
|
346 |
-
|
347 |
-
|
|
|
348 |
continue
|
349 |
|
350 |
-
#
|
351 |
existing = storage.get_peer_address(peer_id, addr_str)
|
352 |
-
|
353 |
-
existing_dt = existing.get("datetime")
|
354 |
-
|
355 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
356 |
|
357 |
# Парсим host и port
|
358 |
proto, hostport = norm.split("://", 1)
|
@@ -380,7 +415,7 @@ def tcp_peer_exchange():
|
|
380 |
sock.settimeout(3)
|
381 |
sock.connect((host, port))
|
382 |
|
383 |
-
#
|
384 |
if storage.is_private(host):
|
385 |
send_addresses = all_addresses
|
386 |
else:
|
@@ -395,8 +430,11 @@ def tcp_peer_exchange():
|
|
395 |
"name": agent_name,
|
396 |
"addresses": send_addresses,
|
397 |
}
|
398 |
-
|
|
|
|
|
399 |
|
|
|
400 |
data = sock.recv(64 * 1024)
|
401 |
sock.close()
|
402 |
|
@@ -404,15 +442,25 @@ def tcp_peer_exchange():
|
|
404 |
print(f"[PeerExchange] No data from {host}:{port}")
|
405 |
continue
|
406 |
|
|
|
|
|
407 |
try:
|
408 |
peers_recv = json.loads(data.decode("utf-8"))
|
|
|
409 |
for p in peers_recv:
|
410 |
-
# Сохраняем только новые адреса или более новые datetime
|
411 |
new_addrs = []
|
412 |
for a in p.get("addresses", []):
|
413 |
-
|
414 |
-
|
415 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
416 |
|
417 |
if new_addrs:
|
418 |
storage.add_or_update_peer(
|
@@ -422,9 +470,10 @@ def tcp_peer_exchange():
|
|
422 |
source="peer_exchange",
|
423 |
status="online"
|
424 |
)
|
|
|
425 |
print(f"[PeerExchange] Received {len(peers_recv)} peers from {host}:{port}")
|
426 |
except Exception as e:
|
427 |
-
print(f"[PeerExchange] Decode error from {host}:{port}
|
428 |
continue
|
429 |
|
430 |
break
|
@@ -463,11 +512,15 @@ def tcp_listener():
|
|
463 |
conn, addr = s.accept()
|
464 |
data = conn.recv(64 * 1024)
|
465 |
if not data:
|
|
|
466 |
conn.close()
|
467 |
continue
|
468 |
|
|
|
|
|
469 |
try:
|
470 |
msg = json.loads(data.decode("utf-8"))
|
|
|
471 |
except Exception as e:
|
472 |
print(f"[TCP Listener] JSON decode error from {addr}: {e}")
|
473 |
conn.close()
|
@@ -478,7 +531,6 @@ def tcp_listener():
|
|
478 |
peer_name = msg.get("name", "unknown")
|
479 |
peer_addrs = msg.get("addresses", [])
|
480 |
|
481 |
-
# Добавляем/обновляем пира только если PoW валидный и datetime новее
|
482 |
valid_addrs = []
|
483 |
for a in peer_addrs:
|
484 |
addr_value = a.get("addr")
|
@@ -489,16 +541,25 @@ def tcp_listener():
|
|
489 |
pubkey = a.get("pubkey")
|
490 |
|
491 |
if not addr_value or nonce is None or not pow_hash or not pubkey:
|
|
|
492 |
continue
|
493 |
|
494 |
-
|
|
|
|
|
495 |
continue
|
496 |
|
497 |
existing = storage.get_peer_address(peer_id, addr_value)
|
498 |
-
|
499 |
-
existing_dt = existing.get("datetime")
|
500 |
-
|
501 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
502 |
|
503 |
valid_addrs.append(a)
|
504 |
|
@@ -510,10 +571,13 @@ def tcp_listener():
|
|
510 |
source="incoming",
|
511 |
status="online"
|
512 |
)
|
|
|
|
|
|
|
513 |
|
514 |
-
print(f"[TCP Listener] Handshake from {peer_id} ({addr})")
|
515 |
|
516 |
-
#
|
517 |
is_lan = storage.is_private(addr[0])
|
518 |
peers_list = []
|
519 |
|
@@ -526,24 +590,34 @@ def tcp_listener():
|
|
526 |
|
527 |
updated_addresses = []
|
528 |
for a in addresses:
|
529 |
-
|
530 |
-
|
|
|
|
|
|
|
531 |
|
532 |
-
|
533 |
-
|
534 |
-
continue
|
535 |
|
536 |
-
|
537 |
-
|
538 |
-
|
539 |
-
|
540 |
-
host = f"{host}%{scope_id}"
|
541 |
|
542 |
-
|
|
|
|
|
|
|
|
|
543 |
|
544 |
-
peers_list.append({
|
|
|
|
|
|
|
545 |
|
|
|
546 |
conn.sendall(json.dumps(peers_list).encode("utf-8"))
|
|
|
547 |
conn.close()
|
548 |
except Exception as e:
|
549 |
print(f"[TCP Listener] Connection handling error: {e}")
|
|
|
8 |
import netifaces
|
9 |
import re
|
10 |
import ipaddress
|
11 |
+
import asyncio
|
12 |
+
import dateutil.parser
|
13 |
+
|
14 |
|
15 |
from datetime import datetime, timezone as UTC
|
16 |
from tools.storage import Storage
|
|
|
137 |
# UDP Discovery
|
138 |
# ---------------------------
|
139 |
def udp_discovery():
|
140 |
+
import dateutil.parser # для парсинга ISO datetime
|
141 |
DISCOVERY_INTERVAL = 30
|
142 |
|
143 |
+
try:
|
144 |
+
# --- Создаём слушающие сокеты один раз ---
|
145 |
+
listen_sockets = []
|
146 |
+
local_addresses = storage.get_config_value("local_addresses", [])
|
147 |
+
print(f"[UDP Discovery] Local addresses (init): {local_addresses}")
|
148 |
+
|
149 |
+
for entry in local_addresses:
|
150 |
+
addr_str = entry.get("addr") if isinstance(entry, dict) else entry
|
151 |
+
if not addr_str:
|
152 |
+
continue
|
153 |
|
154 |
+
proto, hostport = addr_str.split("://", 1)
|
155 |
+
host, port = storage.parse_hostport(hostport)
|
156 |
+
if not port or proto.lower() != "udp":
|
157 |
+
continue
|
|
|
|
|
|
|
158 |
|
159 |
+
# IPv4
|
160 |
+
if not host.startswith("["):
|
161 |
+
try:
|
162 |
+
sock4 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
163 |
+
sock4.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
164 |
+
sock4.bind(("", port))
|
165 |
+
listen_sockets.append(sock4)
|
166 |
+
print(f"[UDP Discovery] Listening IPv4 on *:{port}")
|
167 |
+
except Exception as e:
|
168 |
+
print(f"[UDP Discovery] IPv4 bind failed on port {port}: {e}")
|
169 |
|
170 |
+
# IPv6
|
171 |
+
else:
|
172 |
+
try:
|
173 |
+
sock6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
174 |
+
sock6.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
175 |
+
sock6.bind(("::", port))
|
176 |
+
listen_sockets.append(sock6)
|
177 |
+
print(f"[UDP Discovery] Listening IPv6 on [::]:{port}")
|
178 |
+
except Exception as e:
|
179 |
+
print(f"[UDP Discovery] IPv6 bind failed on port {port}: {e}")
|
180 |
|
181 |
+
except Exception as init_e:
|
182 |
+
print(f"[UDP Discovery] init error: {init_e}")
|
183 |
+
return
|
184 |
+
|
185 |
+
# --- Основной цикл ---
|
186 |
+
agent_pubkey = storage.get_config_value("agent_pubkey")
|
|
|
|
|
|
|
|
|
187 |
|
188 |
+
while True:
|
189 |
+
try:
|
190 |
+
# Приём входящих пакетов
|
191 |
if listen_sockets:
|
192 |
rlist, _, _ = select.select(listen_sockets, [], [], 0.5)
|
193 |
for sock in rlist:
|
194 |
try:
|
195 |
data, addr = sock.recvfrom(2048)
|
196 |
+
print(f"[UDP Discovery] RAW from {addr}: {data!r}")
|
197 |
+
|
198 |
+
try:
|
199 |
+
msg = json.loads(data.decode("utf-8"))
|
200 |
+
except Exception as e:
|
201 |
+
print(f"[UDP Discovery] JSON decode error from {addr}: {e}")
|
202 |
+
continue
|
203 |
+
|
204 |
peer_id = msg.get("id")
|
205 |
if peer_id == my_id:
|
206 |
continue
|
207 |
name = msg.get("name", "unknown")
|
208 |
addresses = msg.get("addresses", [])
|
209 |
|
|
|
210 |
valid_addresses = []
|
211 |
for a in addresses:
|
212 |
addr_str = a.get("addr")
|
|
|
216 |
dt = a.get("datetime")
|
217 |
pubkey = a.get("pubkey")
|
218 |
|
219 |
+
if not addr_str or not pubkey:
|
220 |
continue
|
221 |
|
222 |
# Проверка PoW
|
223 |
+
if nonce is not None and pow_hash and difficulty is not None:
|
224 |
+
ok = storage.verify_pow(peer_id, pubkey, addr_str, nonce, pow_hash, dt, difficulty)
|
225 |
+
print(f"[UDP Discovery] Verify PoW for {addr_str} = {ok}")
|
226 |
+
if not ok:
|
227 |
continue
|
228 |
|
229 |
+
# Проверка datetime
|
230 |
existing = storage.get_peer_address(peer_id, addr_str)
|
231 |
+
try:
|
232 |
+
existing_dt = dateutil.parser.isoparse(existing.get("datetime")) if existing else None
|
233 |
+
dt_obj = dateutil.parser.isoparse(dt) if dt else None
|
234 |
+
except Exception as e:
|
235 |
+
print(f"[UDP Discovery] datetime parse error: {e}")
|
236 |
+
continue
|
237 |
+
|
238 |
+
if existing_dt and dt_obj and existing_dt >= dt_obj:
|
239 |
+
print(f"[UDP Discovery] Skip {addr_str}: old datetime {dt}")
|
240 |
+
continue
|
241 |
|
242 |
valid_addresses.append(a)
|
243 |
|
|
|
249 |
source="discovery",
|
250 |
status="online"
|
251 |
)
|
252 |
+
print(f"[UDP Discovery] Accepted peer {peer_id} ({addr}), {len(valid_addresses)} addresses")
|
253 |
|
254 |
except Exception as e:
|
255 |
print(f"[UDP Discovery] receive error: {e}")
|
256 |
|
257 |
+
# --- Отправка broadcast/multicast ---
|
258 |
+
local_addresses = storage.get_config_value("local_addresses", [])
|
259 |
valid_local_addresses = []
|
260 |
+
|
261 |
for a in local_addresses:
|
262 |
addr_str = a.get("addr") if isinstance(a, dict) else a
|
263 |
nonce = a.get("nonce")
|
264 |
pow_hash = a.get("pow_hash")
|
265 |
difficulty = a.get("difficulty")
|
266 |
dt = a.get("datetime")
|
267 |
+
pubkey = a.get("pubkey") if isinstance(a, dict) else agent_pubkey # self-check
|
268 |
|
269 |
+
if not addr_str:
|
270 |
continue
|
271 |
|
272 |
+
# Проверка PoW только если есть необходимые поля
|
273 |
if nonce is not None and pow_hash and difficulty is not None:
|
274 |
+
ok = storage.verify_pow(my_id, pubkey, addr_str, nonce, pow_hash, dt, difficulty)
|
275 |
+
print(f"[UDP Discovery] Self-check PoW for {addr_str} = {ok}")
|
276 |
+
if not ok:
|
277 |
continue
|
278 |
|
279 |
valid_local_addresses.append(a)
|
|
|
284 |
"addresses": valid_local_addresses
|
285 |
}).encode("utf-8")
|
286 |
|
287 |
+
print(f"[UDP Discovery] Broadcasting: {msg_data}")
|
288 |
+
|
289 |
for entry in valid_local_addresses:
|
290 |
addr_str = entry.get("addr")
|
291 |
proto, hostport = addr_str.split("://", 1)
|
|
|
302 |
try:
|
303 |
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
304 |
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
305 |
+
print(f"[UDP Discovery] Sending broadcast -> {a['broadcast']}:{port}")
|
306 |
sock.sendto(msg_data, (a["broadcast"], port))
|
307 |
sock.close()
|
308 |
+
except Exception as e:
|
309 |
+
print(f"[UDP Discovery] Broadcast error {a['broadcast']}:{port}: {e}")
|
310 |
|
311 |
# IPv6 multicast ff02::1
|
312 |
else:
|
|
|
321 |
sock6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
322 |
sock6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, socket.if_nametoindex(iface))
|
323 |
sock6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, 1)
|
324 |
+
print(f"[UDP Discovery] Sending multicast -> {multicast_addr}:{port}")
|
325 |
sock6.sendto(msg_data, (multicast_addr, port))
|
326 |
sock6.close()
|
327 |
+
except Exception as e:
|
328 |
+
print(f"[UDP Discovery] Multicast error {multicast_addr}:{port}: {e}")
|
329 |
|
330 |
time.sleep(DISCOVERY_INTERVAL)
|
331 |
|
|
|
337 |
# TCP Peer Exchange (исходящие)
|
338 |
# ---------------------------
|
339 |
def tcp_peer_exchange():
|
340 |
+
import dateutil.parser # для корректного парсинга ISO datetime
|
341 |
PEER_EXCHANGE_INTERVAL = 20 # секунды для отладки
|
342 |
|
343 |
while True:
|
|
|
365 |
dt = addr_entry.get("datetime")
|
366 |
pubkey = addr_entry.get("pubkey")
|
367 |
|
|
|
368 |
norm = storage.normalize_address(addr_str)
|
369 |
if not norm:
|
370 |
continue
|
371 |
|
372 |
# Проверка PoW
|
373 |
if nonce is not None and pow_hash and difficulty is not None and pubkey:
|
374 |
+
ok = storage.verify_pow(peer_id, pubkey, addr_str, nonce, pow_hash, dt, difficulty)
|
375 |
+
print(f"[PeerExchange] Verify PoW for {peer_id}@{addr_str} = {ok}")
|
376 |
+
if not ok:
|
377 |
continue
|
378 |
|
379 |
+
# Проверка datetime с использованием dateutil
|
380 |
existing = storage.get_peer_address(peer_id, addr_str)
|
381 |
+
try:
|
382 |
+
existing_dt = dateutil.parser.isoparse(existing.get("datetime")) if existing else None
|
383 |
+
dt_obj = dateutil.parser.isoparse(dt) if dt else None
|
384 |
+
except Exception as e:
|
385 |
+
print(f"[PeerExchange] datetime parse error for {addr_str}: {e}")
|
386 |
+
continue
|
387 |
+
|
388 |
+
if existing_dt and dt_obj and existing_dt >= dt_obj:
|
389 |
+
print(f"[PeerExchange] Skip {addr_str}: old datetime {dt}")
|
390 |
+
continue
|
391 |
|
392 |
# Парсим host и port
|
393 |
proto, hostport = norm.split("://", 1)
|
|
|
415 |
sock.settimeout(3)
|
416 |
sock.connect((host, port))
|
417 |
|
418 |
+
# Отправляем handshake
|
419 |
if storage.is_private(host):
|
420 |
send_addresses = all_addresses
|
421 |
else:
|
|
|
430 |
"name": agent_name,
|
431 |
"addresses": send_addresses,
|
432 |
}
|
433 |
+
raw_handshake = json.dumps(handshake).encode("utf-8")
|
434 |
+
print(f"[PeerExchange] Sending handshake -> {host}:{port}: {raw_handshake}")
|
435 |
+
sock.sendall(raw_handshake)
|
436 |
|
437 |
+
# Читаем ответ
|
438 |
data = sock.recv(64 * 1024)
|
439 |
sock.close()
|
440 |
|
|
|
442 |
print(f"[PeerExchange] No data from {host}:{port}")
|
443 |
continue
|
444 |
|
445 |
+
print(f"[PeerExchange] RAW recv from {host}:{port}: {data!r}")
|
446 |
+
|
447 |
try:
|
448 |
peers_recv = json.loads(data.decode("utf-8"))
|
449 |
+
print(f"[PeerExchange] Parsed recv from {host}:{port}: {peers_recv}")
|
450 |
for p in peers_recv:
|
|
|
451 |
new_addrs = []
|
452 |
for a in p.get("addresses", []):
|
453 |
+
try:
|
454 |
+
existing_addr = storage.get_peer_address(p["id"], a.get("addr"))
|
455 |
+
existing_dt = dateutil.parser.isoparse(existing_addr.get("datetime")) if existing_addr else None
|
456 |
+
dt_obj = dateutil.parser.isoparse(a.get("datetime")) if a.get("datetime") else None
|
457 |
+
if existing_addr is None or (existing_dt and dt_obj and existing_dt < dt_obj) or existing_dt is None:
|
458 |
+
new_addrs.append(a)
|
459 |
+
else:
|
460 |
+
print(f"[PeerExchange] Ignored old {a.get('addr')} from {p['id']}")
|
461 |
+
except Exception as e:
|
462 |
+
print(f"[PeerExchange] Error parsing datetime for {a.get('addr')}: {e}")
|
463 |
+
continue
|
464 |
|
465 |
if new_addrs:
|
466 |
storage.add_or_update_peer(
|
|
|
470 |
source="peer_exchange",
|
471 |
status="online"
|
472 |
)
|
473 |
+
print(f"[PeerExchange] Stored {len(new_addrs)} new addrs for peer {p['id']}")
|
474 |
print(f"[PeerExchange] Received {len(peers_recv)} peers from {host}:{port}")
|
475 |
except Exception as e:
|
476 |
+
print(f"[PeerExchange] Decode error from {host}:{port}: {e}")
|
477 |
continue
|
478 |
|
479 |
break
|
|
|
512 |
conn, addr = s.accept()
|
513 |
data = conn.recv(64 * 1024)
|
514 |
if not data:
|
515 |
+
print(f"[TCP Listener] Empty data from {addr}, closing")
|
516 |
conn.close()
|
517 |
continue
|
518 |
|
519 |
+
print(f"[TCP Listener] RAW recv from {addr}: {data!r}")
|
520 |
+
|
521 |
try:
|
522 |
msg = json.loads(data.decode("utf-8"))
|
523 |
+
print(f"[TCP Listener] Decoded JSON from {addr}: {msg}")
|
524 |
except Exception as e:
|
525 |
print(f"[TCP Listener] JSON decode error from {addr}: {e}")
|
526 |
conn.close()
|
|
|
531 |
peer_name = msg.get("name", "unknown")
|
532 |
peer_addrs = msg.get("addresses", [])
|
533 |
|
|
|
534 |
valid_addrs = []
|
535 |
for a in peer_addrs:
|
536 |
addr_value = a.get("addr")
|
|
|
541 |
pubkey = a.get("pubkey")
|
542 |
|
543 |
if not addr_value or nonce is None or not pow_hash or not pubkey:
|
544 |
+
print(f"[TCP Listener] Skip addr (incomplete): {a}")
|
545 |
continue
|
546 |
|
547 |
+
ok = storage.verify_pow(peer_id, pubkey, addr_value, nonce, pow_hash, dt, difficulty)
|
548 |
+
print(f"[TCP Listener] Verify PoW for {addr_value} = {ok}")
|
549 |
+
if not ok:
|
550 |
continue
|
551 |
|
552 |
existing = storage.get_peer_address(peer_id, addr_value)
|
553 |
+
try:
|
554 |
+
existing_dt = dateutil.parser.isoparse(existing.get("datetime")) if existing else None
|
555 |
+
dt_obj = dateutil.parser.isoparse(dt) if dt else None
|
556 |
+
except Exception as e:
|
557 |
+
print(f"[TCP Listener] datetime parse error for {addr_value}: {e}")
|
558 |
+
continue
|
559 |
+
|
560 |
+
if existing_dt and dt_obj and existing_dt >= dt_obj:
|
561 |
+
print(f"[TCP Listener] Skip old addr {addr_value} (dt={dt})")
|
562 |
+
continue
|
563 |
|
564 |
valid_addrs.append(a)
|
565 |
|
|
|
571 |
source="incoming",
|
572 |
status="online"
|
573 |
)
|
574 |
+
print(f"[TCP Listener] Stored {len(valid_addrs)} addrs for peer {peer_id}")
|
575 |
+
else:
|
576 |
+
print(f"[TCP Listener] No valid addrs from {peer_id}")
|
577 |
|
578 |
+
print(f"[TCP Listener] Handshake from {peer_id} ({addr}) -> name={peer_name}")
|
579 |
|
580 |
+
# Готовим список пиров для ответа
|
581 |
is_lan = storage.is_private(addr[0])
|
582 |
peers_list = []
|
583 |
|
|
|
590 |
|
591 |
updated_addresses = []
|
592 |
for a in addresses:
|
593 |
+
try:
|
594 |
+
proto, hostport = a["addr"].split("://", 1)
|
595 |
+
host, port = storage.parse_hostport(hostport)
|
596 |
+
if not host or not port:
|
597 |
+
continue
|
598 |
|
599 |
+
if not is_lan and not is_public(host):
|
600 |
+
continue
|
|
|
601 |
|
602 |
+
if storage.is_ipv6(host) and host.startswith("fe80:"):
|
603 |
+
scope_id = storage.get_ipv6_scope(host)
|
604 |
+
if scope_id:
|
605 |
+
host = f"{host}%{scope_id}"
|
|
|
606 |
|
607 |
+
updated_addresses.append({
|
608 |
+
"addr": f"{proto}://{host}:{port}"
|
609 |
+
})
|
610 |
+
except Exception:
|
611 |
+
continue
|
612 |
|
613 |
+
peers_list.append({
|
614 |
+
"id": peer_id_local,
|
615 |
+
"addresses": updated_addresses
|
616 |
+
})
|
617 |
|
618 |
+
print(f"[TCP Listener] Sending {len(peers_list)} peers back to {peer_id}")
|
619 |
conn.sendall(json.dumps(peers_list).encode("utf-8"))
|
620 |
+
|
621 |
conn.close()
|
622 |
except Exception as e:
|
623 |
print(f"[TCP Listener] Connection handling error: {e}")
|
agents/requirements.txt
CHANGED
@@ -14,4 +14,5 @@ passlib[bcrypt]
|
|
14 |
werkzeug
|
15 |
itsdangerous
|
16 |
bleach
|
17 |
-
netifaces
|
|
|
|
14 |
werkzeug
|
15 |
itsdangerous
|
16 |
bleach
|
17 |
+
netifaces
|
18 |
+
python-dateutil
|
agents/tools/storage.py
CHANGED
@@ -677,12 +677,14 @@ class Storage:
|
|
677 |
self.conn.commit()
|
678 |
return cursor.lastrowid
|
679 |
|
|
|
680 |
def generate_pow(self, peer_id, pubkey, address, dt=None, difficulty=4):
|
681 |
"""
|
682 |
Генерирует PoW для (peer_id + pubkey + address + datetime).
|
|
|
683 |
"""
|
684 |
if dt is None:
|
685 |
-
dt = datetime.now(
|
686 |
|
687 |
nonce = 0
|
688 |
prefix = "0" * difficulty
|
@@ -1008,10 +1010,12 @@ class Storage:
|
|
1008 |
return did.strip().strip('"').strip("'")
|
1009 |
|
1010 |
# Работа с пирам (agent_peers)
|
|
|
1011 |
@staticmethod
|
1012 |
def verify_pow(peer_id, pubkey, address, nonce, pow_hash, dt, difficulty=4):
|
1013 |
"""
|
1014 |
Проверяет PoW (peer_id + pubkey + address + datetime).
|
|
|
1015 |
"""
|
1016 |
base = f"{peer_id}{pubkey}{address}{dt}{nonce}".encode()
|
1017 |
h = hashlib.sha256(base).hexdigest()
|
@@ -1029,7 +1033,18 @@ class Storage:
|
|
1029 |
norm_addresses = []
|
1030 |
for a in (addresses or []):
|
1031 |
if isinstance(a, dict) and "addr" in a:
|
1032 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1033 |
norm_addresses.append({
|
1034 |
"addr": self.normalize_address(a["addr"]),
|
1035 |
"nonce": a.get("nonce"),
|
@@ -1041,7 +1056,7 @@ class Storage:
|
|
1041 |
"addr": self.normalize_address(a),
|
1042 |
"nonce": None,
|
1043 |
"pow_hash": None,
|
1044 |
-
"datetime": datetime.now(
|
1045 |
})
|
1046 |
|
1047 |
# получаем существующую запись
|
@@ -1120,7 +1135,7 @@ class Storage:
|
|
1120 |
json.dumps(combined_addresses),
|
1121 |
source,
|
1122 |
status,
|
1123 |
-
datetime.now(
|
1124 |
final_pubkey,
|
1125 |
json.dumps(final_capabilities),
|
1126 |
json.dumps(combined_heard_from)
|
|
|
677 |
self.conn.commit()
|
678 |
return cursor.lastrowid
|
679 |
|
680 |
+
# --- Генерация PoW ---
|
681 |
def generate_pow(self, peer_id, pubkey, address, dt=None, difficulty=4):
|
682 |
"""
|
683 |
Генерирует PoW для (peer_id + pubkey + address + datetime).
|
684 |
+
Используется ISO 8601 без микросекунд, UTC.
|
685 |
"""
|
686 |
if dt is None:
|
687 |
+
dt = datetime.now(timezone.utc).replace(microsecond=0).isoformat()
|
688 |
|
689 |
nonce = 0
|
690 |
prefix = "0" * difficulty
|
|
|
1010 |
return did.strip().strip('"').strip("'")
|
1011 |
|
1012 |
# Работа с пирам (agent_peers)
|
1013 |
+
# --- Проверка PoW ---
|
1014 |
@staticmethod
|
1015 |
def verify_pow(peer_id, pubkey, address, nonce, pow_hash, dt, difficulty=4):
|
1016 |
"""
|
1017 |
Проверяет PoW (peer_id + pubkey + address + datetime).
|
1018 |
+
dt ожидается в формате ISO 8601 без микросекунд, UTC.
|
1019 |
"""
|
1020 |
base = f"{peer_id}{pubkey}{address}{dt}{nonce}".encode()
|
1021 |
h = hashlib.sha256(base).hexdigest()
|
|
|
1033 |
norm_addresses = []
|
1034 |
for a in (addresses or []):
|
1035 |
if isinstance(a, dict) and "addr" in a:
|
1036 |
+
# нормализация datetime: ISO 8601 без микросекунд
|
1037 |
+
dt_raw = a.get("datetime")
|
1038 |
+
if dt_raw:
|
1039 |
+
try:
|
1040 |
+
dt_obj = datetime.fromisoformat(dt_raw)
|
1041 |
+
dt_obj = dt_obj.astimezone(timezone.utc).replace(microsecond=0)
|
1042 |
+
dt = dt_obj.isoformat()
|
1043 |
+
except Exception:
|
1044 |
+
dt = datetime.now(timezone.utc).replace(microsecond=0).isoformat()
|
1045 |
+
else:
|
1046 |
+
dt = datetime.now(timezone.utc).replace(microsecond=0).isoformat()
|
1047 |
+
|
1048 |
norm_addresses.append({
|
1049 |
"addr": self.normalize_address(a["addr"]),
|
1050 |
"nonce": a.get("nonce"),
|
|
|
1056 |
"addr": self.normalize_address(a),
|
1057 |
"nonce": None,
|
1058 |
"pow_hash": None,
|
1059 |
+
"datetime": datetime.now(timezone.utc).replace(microsecond=0).isoformat()
|
1060 |
})
|
1061 |
|
1062 |
# получаем существующую запись
|
|
|
1135 |
json.dumps(combined_addresses),
|
1136 |
source,
|
1137 |
status,
|
1138 |
+
datetime.now(timezone.utc).replace(microsecond=0).isoformat(),
|
1139 |
final_pubkey,
|
1140 |
json.dumps(final_capabilities),
|
1141 |
json.dumps(combined_heard_from)
|