# Правило №1: рабочий трафик только через прокси

> **Work traffic to these RPCs MUST go through your proxy. Never call them from the host's main IP.**
>
> **Рабочий трафик к этим RPC ОБЯЗАН идти через твой прокси. Никогда не обращайся к ним с основного IP хоста.**

Это решение владельца проекта и правило №1. Оно выше удобства, скорости и «давай просто быстро проверим».
Обходного флага, переменной окружения или аргумента нет и не будет: клиент `rpc_pool_client` не умеет
ходить к RPC напрямую.

Английский текст выше посимвольно совпадает с `PROXY_RULE` в `rpc_pool_client.pool` и с полем `rule` в блоке
`policy`, который отдаёт API (`POLICY` в `src/rpc_pool/api/policy.py`). Если правило надо цитировать
(в `CLAUDE.md` другого проекта, в логе, в сообщении об ошибке), цитируй этот текст без изменений.

## Чей трафик откуда идёт

| Чей трафик | Откуда идёт |
|---|---|
| Проверки здоровья RPC (health-check) | основной IP сервера пула |
| Сбор каталогов (источники) | основной IP сервера пула |
| Запросы скриптов к самому API пула | напрямую (это свой сервер) |
| **Любые рабочие запросы скриптов к RPC из пула** | **только через прокси скрипта (IP-прокси), никогда с основного IP** |

Что здесь «рабочий запрос»: любой запрос к URL, который ты получил из пула, от кода, который ты запускаешь.
`eth_call`, `eth_getLogs`, `eth_sendRawTransaction`, REST-запрос к Aptos, «просто пинг `eth_chainId`»,
`curl` из терминала для проверки. Всё это идёт через прокси.

Что рабочим запросом не считается:

- запросы к API пула (`GET /v1/chains/{chain}/rpcs`, `POST /v1/report`): сервер твой собственный, клиент ходит к нему напрямую;
- проверки и сбор каталогов, которые делает сам сервер пула: это его основной IP по замыслу.

Прокси клиент берёт из аргумента `proxies`. Из окружения он их не читает: как проект хранит список прокси,
решает проект (например, переменная `WORK_PROXIES` в его `.env`).

## Как правило обеспечивается

### 1. Клиент (`rpc_pool_client`)

- `proxies` в `RpcPool(...)` обязателен: один URL или список. Без него клиент не создаётся, будет `ValueError`:
  `proxies are required. Work traffic to these RPCs MUST go through your proxy. Never call them from the host's main IP. The client has no direct mode.`
- Поддерживаются схемы `http`, `https`, `socks4`, `socks5`, `socks5h`. Для SOCKS порт в URL обязателен.
  Некорректный URL даёт `ValueError` при создании `RpcPool`, а не прямое соединение. В тексте ошибки логин и пароль прокси скрыты.
- Режима «напрямую» нет: ни флага `allow_direct`, ни пустого списка, ни `proxies=None`.
- Каждый запрос к RPC идёт в `aiohttp`-сессии, которая привязана к одному прокси на всю свою жизнь.
  Сессий без прокси клиент не создаёт, а `proxy=None` в отдельном запросе привязку не снимает.
- Если все прокси в cooldown, клиент поднимает `ProxyError` и **не** идёт напрямую (подробнее ниже).
- Проверка самого прокси тоже идёт через этот прокси (`eth_chainId` на RPC проверки).

### 2. Ответы API пула

- Каждый ответ `/v1/*` (в том числе ошибки `401`, `404`, `429`, `500` и публичные `/v1/ping`, `/v1/health`) несёт заголовок
  `X-RPC-Pool-Policy: proxy-required`.
- Ответы `GET /v1/chains/{chain}/rpcs` и `GET /v1/export` содержат блок `policy`:

```json
"policy": {
  "proxy_required": true,
  "rule": "Work traffic to these RPCs MUST go through your proxy. Never call them from the host's main IP.",
  "docs": "docs/RULES.md"
}
```

Клиент, написанный не на Python, должен прочитать этот блок и заголовок и не открывать соединение с RPC без прокси.

### 3. Документация и инструкции для агентов

- Этот файл, `README.md` и проектный `CLAUDE.md` начинаются с правила.
- `docs/INTEGRATION.md` содержит готовый блок для `CLAUDE.md` любого другого проекта.
- Для автоматического подхвата во всех проектах есть скилл `rpc-pool` и строка для глобального `~/.claude/CLAUDE.md`
  (`docs/claude-skill/`). Их ставят только с явного «да» владельца, см. `docs/INTEGRATION.md`.

## Что делать, когда все прокси мертвы

Клиент поднимает `ProxyError`:

```
all 2 proxies are cooling down (the first is back in 43 s); the client never connects directly
```

Правильные действия:

1. **Остановить рабочий трафик.** Дать исключению дойти до верхнего уровня задачи или завершить скрипт с понятной ошибкой.
2. Сказать владельцу, что прокси не отвечают. Число секунд в сообщении подскажет, когда сработает ближайший прокси.
3. Либо подождать (cooldown прокси: 60 с, удваивается при подряд идущих отказах, потолок 900 с), либо заменить прокси на рабочие.
4. Повторить задачу после этого.

Нельзя ни при каких условиях:

- ловить `ProxyError` и повторять запрос напрямую;
- убирать прокси из конфигурации или подменять его пустым значением, «чтобы заработало»;
- брать URL RPC из пула и дёргать его своим HTTP-клиентом («на минуту, только проверить»);
- считать, что «этот RPC всё равно публичный, ничего страшного».

Запросы к API пула во время такого простоя продолжают работать (они идут напрямую), клиент при этом может жить на кеше списка (см. `docs/FAILOVER.md`).

## Что считается нарушением и как сделать правильно

| Нарушение | Как правильно |
|---|---|
| `web3.AsyncHTTPProvider(url)` или `requests.post(url)` с URL из пула | `AsyncWeb3(pool.chain(8453).web3_provider())` или `await pool.chain(8453).call(...)` (сеть задаётся chain id или ключом, не алиасом: `docs/INTEGRATION.md`) |
| Свой `aiohttp.ClientSession()` внутри функции для `run()` | использовать сессию, которую `run` передаёт вторым аргументом: она уже привязана к прокси |
| `curl <url из пула>` без прокси | `curl --noproxy '' -x "$PROXY" ...` (`socks5h://` для DNS через прокси; `--noproxy ''` см. ниже) |
| Жёстко прописанный URL RPC в коде или в конфиге | брать RPC только из пула |
| `except ProxyError:` и повтор без прокси | остановиться (см. выше) |
| Надежда на `HTTP_PROXY`/`ALL_PROXY` из окружения | клиент их игнорирует (`trust_env` у `aiohttp` выключен): передай `proxies=` явно |

**Осторожно, `NO_PROXY` / `no_proxy`.** Если эта переменная окружения задана, `curl -x` для подходящих хостов игнорирует прокси и идёт к ним **напрямую**
(проверено на curl 8.7.1). В каждый `curl` с `-x` добавляй `--noproxy ''`. Python-клиент окружение с прокси не читает
(`trust_env` у `aiohttp` выключен), поэтому `NO_PROXY` ему не мешает, а `HTTP_PROXY` не заменяет `proxies=`.

## Памятка для Claude и других агентов

1. Нужны RPC для любой сети: бери их из пула через `rpc_pool_client` (или HTTP API), не ищи и не придумывай URL.
2. В проекте нет прокси: не продолжай «пока без них». Остановись и спроси владельца.
3. RPC не отвечает: ничего не делай руками. Клиент сам перейдёт к следующему RPC по кругу и сам отправит жалобу в пул.
4. Не пиши свои циклы повторов вокруг отдельных URL: перебор RPC по кругу делает клиент. Ловить нужно только итоговые исключения (`docs/FAILOVER.md`, раздел «Исключения»).
5. Что-то непонятно в правиле: перечитай этот файл, правило не обсуждается.
