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)
|