GitHub Action
commited on
Commit
·
ab3306e
1
Parent(s):
bcc313a
Sync from GitHub with Git LFS
Browse files- agents/bootstrap.txt +0 -4
- agents/examples/bootstrap.txt +4 -0
- agents/init.py +59 -9
- agents/peer_sync.py +86 -21
- agents/tools/db_structure.sql +1 -3
- agents/tools/storage.py +57 -30
agents/bootstrap.txt
CHANGED
|
@@ -1,4 +0,0 @@
|
|
| 1 |
-
did:hmp:ac0e063e-8609-4ef9-75f8-d7dcafc65977 ["tcp://node1.mesh.local:8000","udp://node1.mesh.local:8030"]
|
| 2 |
-
did:hmp:ac0e063e-8709-4ef9-75f8-d7dcafc65977 ["tcp://node2.mesh.local:8010"]
|
| 3 |
-
did:hmp:ac0e063e-8609-4ff9-75f8-d7dc46c65977 ["tcp://node3.mesh.local:8020"]
|
| 4 |
-
did:hmp:ac0f053e-8609-4ef9-75f8-d7dcafc65977 ["any://node4.mesh.local:8000"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agents/examples/bootstrap.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
did:hmp:ac0e063e-8609-4ef9-75f8-d7dcafc65977 ["tcp://node1.mesh.local:8000","udp://node1.mesh.local:8030"] "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA+J7pfdbWotIo31Omd0b49bIFAc6nCZpriQc9BdbZ2TQ=\n-----END PUBLIC KEY-----\n" 12166 "0000967e52fdbeb393ec9e4376cf32605209da69fabc590607cea0574273e470"
|
| 2 |
+
did:hmp:ac0e063e-8709-4ef9-75f8-d7dcafc65977 ["tcp://node2.mesh.local:8010"] "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA6OZO4dJ9IZethuM+8f+m8WVRXwMsCPOYaNrwcaA1a08=\n-----END PUBLIC KEY-----\n" 82022 "00004a512b13ca5084fa913e890578befe1068ac06d3dba541589822a90a36c6"
|
| 3 |
+
did:hmp:ac0e063e-8609-4ff9-75f8-d7dc46c65977 ["tcp://node3.mesh.local:8020"] "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAAGTXI+KznuhV1N9+sb9/FHQndgmw2Ro2dAvwpRBrG78=\n-----END PUBLIC KEY-----\n" 47803 "0000beb657e738dabc9731eb911f45f6706fba7a8bc342583d022b004ce97921"
|
| 4 |
+
did:hmp:ac0f053e-8609-4ef9-75f8-d7dcafc65977 ["any://node4.mesh.local:8000"] "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA3HR98+9G+MbrAuMKx5mhGKccOlWdre9A+OKE1W0gMa4=\n-----END PUBLIC KEY-----\n" 13549 "00008b71f46aa0f0c9e3f15a6b32a24ef58a1642936e324a548b5aac97e989d6"
|
agents/init.py
CHANGED
|
@@ -48,11 +48,7 @@ def init_identity(storage, config):
|
|
| 48 |
privkey, pubkey = generate_keypair(method="ed25519")
|
| 49 |
privkey, pubkey = privkey.decode(), pubkey.decode()
|
| 50 |
|
| 51 |
-
# 3.
|
| 52 |
-
addresses = config.get("local_addresses", [])
|
| 53 |
-
nonce, pow_hash = storage.generate_pow(identity_id, pubkey, addresses, difficulty=4)
|
| 54 |
-
|
| 55 |
-
# 4. Создать запись в identity
|
| 56 |
identity = {
|
| 57 |
"id": identity_id,
|
| 58 |
"name": config.get("agent_name", "Unnamed"),
|
|
@@ -64,16 +60,14 @@ def init_identity(storage, config):
|
|
| 64 |
}
|
| 65 |
storage.add_identity(identity)
|
| 66 |
|
| 67 |
-
#
|
| 68 |
config["agent_id"] = did
|
| 69 |
config["identity_agent"] = identity_id
|
| 70 |
config["pubkey"] = pubkey
|
| 71 |
config["privkey"] = privkey
|
| 72 |
-
config["pow_nonce"] = nonce
|
| 73 |
-
config["pow_hash"] = pow_hash
|
| 74 |
|
| 75 |
save_config(CONFIG_PATH, config)
|
| 76 |
-
print(f"[+] Создана личность: {identity_id}
|
| 77 |
else:
|
| 78 |
print("[=] agent_id уже задан, пропускаем генерацию DiD.")
|
| 79 |
|
|
@@ -132,10 +126,65 @@ def init_config_table(storage, config):
|
|
| 132 |
else:
|
| 133 |
flat_config[addr_key] = expand_addresses(value)
|
| 134 |
|
|
|
|
| 135 |
for key, value in flat_config.items():
|
| 136 |
storage.set_config(key, json.dumps(value))
|
| 137 |
print("[+] Конфигурация сохранена в БД.")
|
| 138 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
def init_prompts_and_ethics():
|
| 140 |
folder = os.path.dirname(__file__)
|
| 141 |
prompt_files = [
|
|
@@ -246,6 +295,7 @@ def ensure_db_initialized():
|
|
| 246 |
init_user(storage, config)
|
| 247 |
init_llm_backends(storage, config)
|
| 248 |
init_config_table(storage, config)
|
|
|
|
| 249 |
save_config(CONFIG_PATH, config)
|
| 250 |
init_prompts_and_ethics()
|
| 251 |
except Exception as e:
|
|
|
|
| 48 |
privkey, pubkey = generate_keypair(method="ed25519")
|
| 49 |
privkey, pubkey = privkey.decode(), pubkey.decode()
|
| 50 |
|
| 51 |
+
# 3. Создать запись в identity
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
identity = {
|
| 53 |
"id": identity_id,
|
| 54 |
"name": config.get("agent_name", "Unnamed"),
|
|
|
|
| 60 |
}
|
| 61 |
storage.add_identity(identity)
|
| 62 |
|
| 63 |
+
# 4. Записать в config
|
| 64 |
config["agent_id"] = did
|
| 65 |
config["identity_agent"] = identity_id
|
| 66 |
config["pubkey"] = pubkey
|
| 67 |
config["privkey"] = privkey
|
|
|
|
|
|
|
| 68 |
|
| 69 |
save_config(CONFIG_PATH, config)
|
| 70 |
+
print(f"[+] Создана личность: {identity_id}.")
|
| 71 |
else:
|
| 72 |
print("[=] agent_id уже задан, пропускаем генерацию DiD.")
|
| 73 |
|
|
|
|
| 126 |
else:
|
| 127 |
flat_config[addr_key] = expand_addresses(value)
|
| 128 |
|
| 129 |
+
# Сохраняем конфиг в БД
|
| 130 |
for key, value in flat_config.items():
|
| 131 |
storage.set_config(key, json.dumps(value))
|
| 132 |
print("[+] Конфигурация сохранена в БД.")
|
| 133 |
|
| 134 |
+
def update_pow_for_addresses(storage, difficulty=4):
|
| 135 |
+
raw_id = storage.get_config_value("agent_id")
|
| 136 |
+
if not raw_id:
|
| 137 |
+
print("[-] Нет agent_id в config — пропуск обновления PoW.")
|
| 138 |
+
return
|
| 139 |
+
agent_id = storage.normalize_did(raw_id)
|
| 140 |
+
pubkey = storage.get_config_value("pubkey")
|
| 141 |
+
|
| 142 |
+
if not agent_id or not pubkey:
|
| 143 |
+
print("[-] Нет agent_id/pubkey в config — пропуск обновления PoW.")
|
| 144 |
+
return
|
| 145 |
+
|
| 146 |
+
for addr_key in ("local_addresses", "global_addresses"):
|
| 147 |
+
raw = storage.get_config_value(addr_key)
|
| 148 |
+
if not raw:
|
| 149 |
+
continue
|
| 150 |
+
|
| 151 |
+
# raw может быть либо JSON-строкой, либо уже list
|
| 152 |
+
if isinstance(raw, str):
|
| 153 |
+
try:
|
| 154 |
+
addresses = json.loads(raw)
|
| 155 |
+
except Exception as e:
|
| 156 |
+
print(f"[!] Ошибка при чтении {addr_key}: {e}")
|
| 157 |
+
continue
|
| 158 |
+
else:
|
| 159 |
+
addresses = raw
|
| 160 |
+
|
| 161 |
+
enriched = []
|
| 162 |
+
for addr in addresses:
|
| 163 |
+
# если уже dict → PoW записан
|
| 164 |
+
if isinstance(addr, dict):
|
| 165 |
+
enriched.append(addr)
|
| 166 |
+
continue
|
| 167 |
+
|
| 168 |
+
nonce, hash_value = storage.generate_pow(
|
| 169 |
+
peer_id=agent_id,
|
| 170 |
+
pubkey=pubkey,
|
| 171 |
+
address=addr,
|
| 172 |
+
difficulty=difficulty
|
| 173 |
+
)
|
| 174 |
+
enriched.append({
|
| 175 |
+
"address": addr,
|
| 176 |
+
"expires": "",
|
| 177 |
+
"pow": {
|
| 178 |
+
"nonce": nonce,
|
| 179 |
+
"hash": hash_value,
|
| 180 |
+
"difficulty": difficulty
|
| 181 |
+
}
|
| 182 |
+
})
|
| 183 |
+
|
| 184 |
+
storage.set_config(addr_key, json.dumps(enriched))
|
| 185 |
+
|
| 186 |
+
print("[+] Адреса обновлены с PoW.")
|
| 187 |
+
|
| 188 |
def init_prompts_and_ethics():
|
| 189 |
folder = os.path.dirname(__file__)
|
| 190 |
prompt_files = [
|
|
|
|
| 295 |
init_user(storage, config)
|
| 296 |
init_llm_backends(storage, config)
|
| 297 |
init_config_table(storage, config)
|
| 298 |
+
update_pow_for_addresses(storage, difficulty=4)
|
| 299 |
save_config(CONFIG_PATH, config)
|
| 300 |
init_prompts_and_ethics()
|
| 301 |
except Exception as e:
|
agents/peer_sync.py
CHANGED
|
@@ -24,13 +24,35 @@ global_addresses = storage.get_config_value("global_addresses", [])
|
|
| 24 |
all_addresses = local_addresses + global_addresses # один раз
|
| 25 |
|
| 26 |
# Получаем уникальные локальные порты
|
| 27 |
-
def get_local_ports():
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
local_ports = get_local_ports()
|
| 36 |
print(f"[PeerSync] Local ports: {local_ports}")
|
|
@@ -38,12 +60,7 @@ print(f"[PeerSync] Local ports: {local_ports}")
|
|
| 38 |
# ---------------------------
|
| 39 |
# Загрузка bootstrap
|
| 40 |
# ---------------------------
|
| 41 |
-
|
| 42 |
def load_bootstrap_peers(filename="bootstrap.txt"):
|
| 43 |
-
"""
|
| 44 |
-
Читает bootstrap.txt и добавляет узлы в storage.
|
| 45 |
-
Формат строки: did [JSON-список адресов]
|
| 46 |
-
"""
|
| 47 |
try:
|
| 48 |
with open(filename, "r", encoding="utf-8") as f:
|
| 49 |
lines = f.readlines()
|
|
@@ -56,19 +73,57 @@ def load_bootstrap_peers(filename="bootstrap.txt"):
|
|
| 56 |
if not line or line.startswith("#"):
|
| 57 |
continue
|
| 58 |
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
|
|
|
| 62 |
continue
|
| 63 |
-
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
try:
|
| 66 |
addresses = json.loads(addresses_json)
|
| 67 |
except Exception as e:
|
| 68 |
-
print(f"[Bootstrap]
|
| 69 |
continue
|
|
|
|
| 70 |
|
| 71 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
expanded_addresses = []
|
| 73 |
for addr in addresses:
|
| 74 |
if addr.startswith("any://"):
|
|
@@ -78,8 +133,18 @@ def load_bootstrap_peers(filename="bootstrap.txt"):
|
|
| 78 |
else:
|
| 79 |
expanded_addresses.append(addr)
|
| 80 |
|
| 81 |
-
storage.add_or_update_peer(
|
| 82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
print(f"[Bootstrap] Loaded peer {did} -> {expanded_addresses}")
|
| 84 |
|
| 85 |
# ---------------------------
|
|
|
|
| 24 |
all_addresses = local_addresses + global_addresses # один раз
|
| 25 |
|
| 26 |
# Получаем уникальные локальные порты
|
| 27 |
+
def get_local_ports(storage):
|
| 28 |
+
"""
|
| 29 |
+
Возвращает список портов для всех локальных адресов.
|
| 30 |
+
Формат конфигурации: список dict {"addr": str, "nonce": int, "pow_hash": str, "expires": ...}
|
| 31 |
+
"""
|
| 32 |
+
local_addrs_json = storage.get_config("local_addresses")
|
| 33 |
+
if not local_addrs_json:
|
| 34 |
+
return []
|
| 35 |
+
|
| 36 |
+
try:
|
| 37 |
+
local_addrs = json.loads(local_addrs_json)
|
| 38 |
+
except:
|
| 39 |
+
print("[WARN] Не удалось разобрать local_addresses из БД")
|
| 40 |
+
return []
|
| 41 |
+
|
| 42 |
+
ports = []
|
| 43 |
+
for entry in local_addrs:
|
| 44 |
+
# Если entry — словарь, берём поле addr, иначе предполагаем строку
|
| 45 |
+
addr_str = entry["addr"] if isinstance(entry, dict) else entry
|
| 46 |
+
|
| 47 |
+
# Разбираем протокол и host:port
|
| 48 |
+
try:
|
| 49 |
+
proto, hostport = addr_str.split("://", 1)
|
| 50 |
+
_, port = storage.parse_hostport(hostport)
|
| 51 |
+
ports.append(port)
|
| 52 |
+
except Exception as e:
|
| 53 |
+
print(f"[WARN] Не удалось разобрать адрес {addr_str}: {e}")
|
| 54 |
+
|
| 55 |
+
return ports
|
| 56 |
|
| 57 |
local_ports = get_local_ports()
|
| 58 |
print(f"[PeerSync] Local ports: {local_ports}")
|
|
|
|
| 60 |
# ---------------------------
|
| 61 |
# Загрузка bootstrap
|
| 62 |
# ---------------------------
|
|
|
|
| 63 |
def load_bootstrap_peers(filename="bootstrap.txt"):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
try:
|
| 65 |
with open(filename, "r", encoding="utf-8") as f:
|
| 66 |
lines = f.readlines()
|
|
|
|
| 73 |
if not line or line.startswith("#"):
|
| 74 |
continue
|
| 75 |
|
| 76 |
+
# 1. DID
|
| 77 |
+
did_end = line.find(" ")
|
| 78 |
+
if did_end == -1:
|
| 79 |
+
print(f"[Bootstrap] Invalid line (no DID): {line}")
|
| 80 |
continue
|
| 81 |
+
did = line[:did_end]
|
| 82 |
+
rest = line[did_end + 1:].strip()
|
| 83 |
+
|
| 84 |
+
# 2. JSON-адреса
|
| 85 |
+
addr_start = rest.find("[")
|
| 86 |
+
addr_end = rest.find("]") + 1
|
| 87 |
+
if addr_start == -1 or addr_end == 0:
|
| 88 |
+
print(f"[Bootstrap] Invalid JSON addresses: {line}")
|
| 89 |
+
continue
|
| 90 |
+
addresses_json = rest[addr_start:addr_end]
|
| 91 |
try:
|
| 92 |
addresses = json.loads(addresses_json)
|
| 93 |
except Exception as e:
|
| 94 |
+
print(f"[Bootstrap] Failed to parse JSON addresses: {line} ({e})")
|
| 95 |
continue
|
| 96 |
+
rest = rest[addr_end:].strip()
|
| 97 |
|
| 98 |
+
# 3. pubkey (в кавычках)
|
| 99 |
+
pub_start = rest.find('"')
|
| 100 |
+
pub_end = rest.find('"', pub_start + 1)
|
| 101 |
+
if pub_start == -1 or pub_end == -1:
|
| 102 |
+
print(f"[Bootstrap] Invalid pubkey: {line}")
|
| 103 |
+
continue
|
| 104 |
+
pubkey = rest[pub_start + 1:pub_end].replace("\\n", "\n")
|
| 105 |
+
rest = rest[pub_end + 1:].strip()
|
| 106 |
+
|
| 107 |
+
# 4. pow_nonce
|
| 108 |
+
nonce_end = rest.find(" ")
|
| 109 |
+
if nonce_end == -1:
|
| 110 |
+
print(f"[Bootstrap] Invalid pow_nonce: {line}")
|
| 111 |
+
continue
|
| 112 |
+
try:
|
| 113 |
+
pow_nonce = int(rest[:nonce_end])
|
| 114 |
+
except ValueError:
|
| 115 |
+
print(f"[Bootstrap] Invalid pow_nonce: {rest[:nonce_end]} in line: {line}")
|
| 116 |
+
continue
|
| 117 |
+
rest = rest[nonce_end:].strip()
|
| 118 |
+
|
| 119 |
+
# 5. pow_hash (в кавычках)
|
| 120 |
+
if rest.startswith('"') and rest.endswith('"'):
|
| 121 |
+
pow_hash = rest[1:-1]
|
| 122 |
+
else:
|
| 123 |
+
print(f"[Bootstrap] Invalid pow_hash: {line}")
|
| 124 |
+
continue
|
| 125 |
+
|
| 126 |
+
# Разворачиваем any://
|
| 127 |
expanded_addresses = []
|
| 128 |
for addr in addresses:
|
| 129 |
if addr.startswith("any://"):
|
|
|
|
| 133 |
else:
|
| 134 |
expanded_addresses.append(addr)
|
| 135 |
|
| 136 |
+
storage.add_or_update_peer(
|
| 137 |
+
peer_id=did,
|
| 138 |
+
name=None,
|
| 139 |
+
addresses=expanded_addresses,
|
| 140 |
+
source="bootstrap",
|
| 141 |
+
status="offline",
|
| 142 |
+
pubkey=pubkey,
|
| 143 |
+
capabilities=None,
|
| 144 |
+
pow_nonce=pow_nonce,
|
| 145 |
+
pow_hash=pow_hash
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
print(f"[Bootstrap] Loaded peer {did} -> {expanded_addresses}")
|
| 149 |
|
| 150 |
# ---------------------------
|
agents/tools/db_structure.sql
CHANGED
|
@@ -151,7 +151,7 @@ CREATE TABLE IF NOT EXISTS llm_recent_responses (
|
|
| 151 |
CREATE TABLE IF NOT EXISTS agent_peers (
|
| 152 |
id TEXT PRIMARY KEY, -- Уникальный идентификатор (UUID или псевдоним)
|
| 153 |
name TEXT, -- Имя агента
|
| 154 |
-
addresses TEXT, -- Адреса для связи (JSON)
|
| 155 |
tags TEXT, -- Теги (Postman, Friend и т.д.)
|
| 156 |
status TEXT DEFAULT 'unknown', -- online | offline | untrusted | blacklisted и др.
|
| 157 |
source TEXT, -- bootstrap | discovery | exchange
|
|
@@ -159,8 +159,6 @@ CREATE TABLE IF NOT EXISTS agent_peers (
|
|
| 159 |
description TEXT, -- Описание агента
|
| 160 |
capabilities TEXT, -- Возможности (JSON)
|
| 161 |
pubkey TEXT, -- Публичный ключ
|
| 162 |
-
pow_nonce INTEGER, -- Nonce для PoW
|
| 163 |
-
pow_hash TEXT, -- Контрольный хеш PoW (например, sha256(pubkey + addresses + nonce))
|
| 164 |
heard_from TEXT, -- JSON список DID, от кого агент о нем узнал
|
| 165 |
software_info TEXT, -- Информация о ПО агента (JSON)
|
| 166 |
registered_at DATETIME DEFAULT CURRENT_TIMESTAMP -- Время регистрации
|
|
|
|
| 151 |
CREATE TABLE IF NOT EXISTS agent_peers (
|
| 152 |
id TEXT PRIMARY KEY, -- Уникальный идентификатор (UUID или псевдоним)
|
| 153 |
name TEXT, -- Имя агента
|
| 154 |
+
addresses TEXT, -- Адреса для связи (JSON), каждый адрес содержит addr, nonce, pow_hash, expiries
|
| 155 |
tags TEXT, -- Теги (Postman, Friend и т.д.)
|
| 156 |
status TEXT DEFAULT 'unknown', -- online | offline | untrusted | blacklisted и др.
|
| 157 |
source TEXT, -- bootstrap | discovery | exchange
|
|
|
|
| 159 |
description TEXT, -- Описание агента
|
| 160 |
capabilities TEXT, -- Возможности (JSON)
|
| 161 |
pubkey TEXT, -- Публичный ключ
|
|
|
|
|
|
|
| 162 |
heard_from TEXT, -- JSON список DID, от кого агент о нем узнал
|
| 163 |
software_info TEXT, -- Информация о ПО агента (JSON)
|
| 164 |
registered_at DATETIME DEFAULT CURRENT_TIMESTAMP -- Время регистрации
|
agents/tools/storage.py
CHANGED
|
@@ -677,11 +677,15 @@ class Storage:
|
|
| 677 |
self.conn.commit()
|
| 678 |
return cursor.lastrowid
|
| 679 |
|
| 680 |
-
def generate_pow(self, peer_id, pubkey,
|
|
|
|
|
|
|
|
|
|
| 681 |
nonce = 0
|
| 682 |
prefix = "0" * difficulty
|
|
|
|
| 683 |
while True:
|
| 684 |
-
base = f"{peer_id}{pubkey}{
|
| 685 |
h = hashlib.sha256(base).hexdigest()
|
| 686 |
if h.startswith(prefix):
|
| 687 |
return nonce, h
|
|
@@ -963,9 +967,19 @@ class Storage:
|
|
| 963 |
|
| 964 |
return f"{proto}://{host}:{port}" if port else f"{proto}://{host}"
|
| 965 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 966 |
# Работа с пирам (agent_peers)
|
| 967 |
-
|
| 968 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 969 |
h = hashlib.sha256(base).hexdigest()
|
| 970 |
return h == pow_hash and h.startswith("0" * difficulty)
|
| 971 |
|
|
@@ -973,14 +987,24 @@ class Storage:
|
|
| 973 |
self, peer_id, name, addresses,
|
| 974 |
source="discovery", status="unknown",
|
| 975 |
pubkey=None, capabilities=None,
|
| 976 |
-
pow_nonce=None, pow_hash=None,
|
| 977 |
heard_from=None
|
| 978 |
):
|
| 979 |
c = self.conn.cursor()
|
| 980 |
|
| 981 |
-
#
|
| 982 |
-
|
| 983 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 984 |
existing_addresses = []
|
| 985 |
existing_pubkey = None
|
| 986 |
existing_capabilities = {}
|
|
@@ -1004,37 +1028,42 @@ class Storage:
|
|
| 1004 |
except:
|
| 1005 |
existing_heard_from = []
|
| 1006 |
|
| 1007 |
-
# объединяем адреса
|
| 1008 |
-
combined_addresses = list({self.normalize_address(a) for a in (*existing_addresses, *addresses)})
|
| 1009 |
-
final_pubkey = pubkey or existing_pubkey
|
| 1010 |
final_capabilities = capabilities or existing_capabilities
|
| 1011 |
-
|
| 1012 |
-
# обновляем heard_from
|
| 1013 |
combined_heard_from = list(set(existing_heard_from + (heard_from or [])))
|
| 1014 |
|
| 1015 |
-
#
|
| 1016 |
-
if
|
| 1017 |
-
|
| 1018 |
-
|
| 1019 |
-
|
| 1020 |
-
|
| 1021 |
-
|
| 1022 |
-
|
| 1023 |
-
|
| 1024 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1025 |
c.execute("""
|
| 1026 |
-
INSERT INTO agent_peers (id, name, addresses, source, status, last_seen, pubkey, capabilities,
|
| 1027 |
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?,
|
| 1028 |
ON CONFLICT(id) DO UPDATE SET
|
| 1029 |
name=excluded.name,
|
| 1030 |
addresses=excluded.addresses,
|
| 1031 |
source=excluded.source,
|
| 1032 |
status=excluded.status,
|
| 1033 |
last_seen=excluded.last_seen,
|
| 1034 |
-
pubkey=excluded.pubkey,
|
| 1035 |
capabilities=excluded.capabilities,
|
| 1036 |
-
pow_nonce=excluded.pow_nonce,
|
| 1037 |
-
pow_hash=excluded.pow_hash,
|
| 1038 |
heard_from=excluded.heard_from
|
| 1039 |
""", (
|
| 1040 |
peer_id,
|
|
@@ -1045,8 +1074,6 @@ class Storage:
|
|
| 1045 |
datetime.now(UTC).isoformat(),
|
| 1046 |
final_pubkey,
|
| 1047 |
json.dumps(final_capabilities),
|
| 1048 |
-
pow_nonce,
|
| 1049 |
-
pow_hash,
|
| 1050 |
json.dumps(combined_heard_from)
|
| 1051 |
))
|
| 1052 |
self.conn.commit()
|
|
|
|
| 677 |
self.conn.commit()
|
| 678 |
return cursor.lastrowid
|
| 679 |
|
| 680 |
+
def generate_pow(self, peer_id, pubkey, address, expires=None, difficulty=4):
|
| 681 |
+
"""
|
| 682 |
+
Генерирует PoW для одной пары (peer_id + pubkey + address + expires).
|
| 683 |
+
"""
|
| 684 |
nonce = 0
|
| 685 |
prefix = "0" * difficulty
|
| 686 |
+
expires_str = str(expires) if expires is not None else ""
|
| 687 |
while True:
|
| 688 |
+
base = f"{peer_id}{pubkey}{address}{expires_str}{nonce}".encode()
|
| 689 |
h = hashlib.sha256(base).hexdigest()
|
| 690 |
if h.startswith(prefix):
|
| 691 |
return nonce, h
|
|
|
|
| 967 |
|
| 968 |
return f"{proto}://{host}:{port}" if port else f"{proto}://{host}"
|
| 969 |
|
| 970 |
+
# Нормализация DID
|
| 971 |
+
@staticmethod
|
| 972 |
+
def normalize_did(did: str) -> str:
|
| 973 |
+
return did.strip().strip('"').strip("'")
|
| 974 |
+
|
| 975 |
# Работа с пирам (agent_peers)
|
| 976 |
+
@staticmethod
|
| 977 |
+
def verify_pow(peer_id, pubkey, address, nonce, pow_hash, expires=None, difficulty=4):
|
| 978 |
+
"""
|
| 979 |
+
Проверяет PoW для одной пары (peer_id + pubkey + address + expires).
|
| 980 |
+
"""
|
| 981 |
+
expires_str = str(expires) if expires is not None else ""
|
| 982 |
+
base = f"{peer_id}{pubkey}{address}{expires_str}{nonce}".encode()
|
| 983 |
h = hashlib.sha256(base).hexdigest()
|
| 984 |
return h == pow_hash and h.startswith("0" * difficulty)
|
| 985 |
|
|
|
|
| 987 |
self, peer_id, name, addresses,
|
| 988 |
source="discovery", status="unknown",
|
| 989 |
pubkey=None, capabilities=None,
|
|
|
|
| 990 |
heard_from=None
|
| 991 |
):
|
| 992 |
c = self.conn.cursor()
|
| 993 |
|
| 994 |
+
# нормализуем входные адреса
|
| 995 |
+
norm_addresses = []
|
| 996 |
+
for a in (addresses or []):
|
| 997 |
+
if isinstance(a, dict) and "addr" in a:
|
| 998 |
+
norm_addresses.append({
|
| 999 |
+
"addr": self.normalize_address(a["addr"]),
|
| 1000 |
+
"nonce": a.get("nonce"),
|
| 1001 |
+
"pow_hash": a.get("pow_hash"),
|
| 1002 |
+
"expires": a.get("expires")
|
| 1003 |
+
})
|
| 1004 |
+
elif isinstance(a, str):
|
| 1005 |
+
norm_addresses.append({"addr": self.normalize_address(a), "nonce": None, "pow_hash": None, "expires": None})
|
| 1006 |
+
|
| 1007 |
+
# получаем существующую запись
|
| 1008 |
existing_addresses = []
|
| 1009 |
existing_pubkey = None
|
| 1010 |
existing_capabilities = {}
|
|
|
|
| 1028 |
except:
|
| 1029 |
existing_heard_from = []
|
| 1030 |
|
|
|
|
|
|
|
|
|
|
| 1031 |
final_capabilities = capabilities or existing_capabilities
|
|
|
|
|
|
|
| 1032 |
combined_heard_from = list(set(existing_heard_from + (heard_from or [])))
|
| 1033 |
|
| 1034 |
+
# Проверка неизменности pubkey
|
| 1035 |
+
if existing_pubkey and pubkey and existing_pubkey != pubkey:
|
| 1036 |
+
print(f"[WARN] Peer {peer_id} pubkey mismatch! Possible impersonation attempt.")
|
| 1037 |
+
return
|
| 1038 |
+
final_pubkey = existing_pubkey or pubkey
|
| 1039 |
+
|
| 1040 |
+
# Объединяем адреса по addr, проверяем PoW
|
| 1041 |
+
addr_map = {a["addr"]: a for a in existing_addresses if isinstance(a, dict)}
|
| 1042 |
+
for a in norm_addresses:
|
| 1043 |
+
addr = a["addr"]
|
| 1044 |
+
nonce = a.get("nonce")
|
| 1045 |
+
pow_hash = a.get("pow_hash")
|
| 1046 |
+
expires = a.get("expires")
|
| 1047 |
+
# проверка PoW
|
| 1048 |
+
if nonce is not None and pow_hash is not None:
|
| 1049 |
+
if not self.verify_pow(peer_id, final_pubkey, addr, nonce, pow_hash, expires):
|
| 1050 |
+
print(f"[WARN] Peer {peer_id} address {addr} failed PoW validation")
|
| 1051 |
+
continue
|
| 1052 |
+
addr_map[addr] = {"addr": addr, "nonce": nonce, "pow_hash": pow_hash, "expires": expires}
|
| 1053 |
+
|
| 1054 |
+
combined_addresses = list(addr_map.values())
|
| 1055 |
+
|
| 1056 |
+
# Вставка/обновление записи
|
| 1057 |
c.execute("""
|
| 1058 |
+
INSERT INTO agent_peers (id, name, addresses, source, status, last_seen, pubkey, capabilities, heard_from)
|
| 1059 |
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
| 1060 |
ON CONFLICT(id) DO UPDATE SET
|
| 1061 |
name=excluded.name,
|
| 1062 |
addresses=excluded.addresses,
|
| 1063 |
source=excluded.source,
|
| 1064 |
status=excluded.status,
|
| 1065 |
last_seen=excluded.last_seen,
|
|
|
|
| 1066 |
capabilities=excluded.capabilities,
|
|
|
|
|
|
|
| 1067 |
heard_from=excluded.heard_from
|
| 1068 |
""", (
|
| 1069 |
peer_id,
|
|
|
|
| 1074 |
datetime.now(UTC).isoformat(),
|
| 1075 |
final_pubkey,
|
| 1076 |
json.dumps(final_capabilities),
|
|
|
|
|
|
|
| 1077 |
json.dumps(combined_heard_from)
|
| 1078 |
))
|
| 1079 |
self.conn.commit()
|