Разработчикам

Интеграция провайдера

10 мин чтенияОткрыть как Markdown ↗

Запускайте провайдер URnetwork в приложении. Каждая установка раздаёт соединение как клиент вашей сети и зарабатывает на один постоянный кошелёк Bittensor.

Интеграция провайдера — ваш бэкенд оформляет каждую провайдерскую установку для вашего приложения на устройстве пользователя, приложение раздаёт соединение через URnetwork клиентам, а согласие сети от владельца coldkey, один раз подписанное офлайн и отправленное вашим бэкендом, направляет выплаты подсети на постоянный coldkey для выплат; ни один ключ не поставляется внутри приложения

Провайдер раздаёт интернет-соединение устройства: трафик других пользователей URnetwork выходит через него. Пока он работает, приложение показывает четыре вещи: идёт ли раздача, скольких клиентов он обслужил, сколько данных передал и кошелёк для выплат, только для чтения. Итоги заработка берутся из вашего бэкенда. Готовые примеры есть для двенадцати языков и трёх собственных платформ: Electron, Tauri и Android. Все они реализуют контракт провайдера, где закреплены точные правила; эта страница объясняет, как части сочетаются друг с другом.

Кто что хранит

Интеграция провайдера делит работу между тремя сторонами, и ни один ключ не поставляется внутри приложения.

  • Ваш бэкенд хранит корневой JWT вашей сети. Он оформляет каждую установку как провайдерскую установку (provider install), передаёт каждой установке её ограниченный клиентский JWT и запрашивает сообщения согласия для кошелька.
  • Владелец coldkey подписывает каждое согласие для кошелька офлайн, своим собственным кошельковым инструментом, например btcli, расширением polkadot.js или subkey. Секрет coldkey никогда не попадает ни на ваш бэкенд, ни в приложение.
  • Приложение запускает локальный Device SDK со своим клиентским JWT, публично раздаёт соединение и показывает статус. Оно никогда не хранит корневой JWT и никогда не подписывает и не отправляет согласие.
ЗначениеГде хранитсяНазначение
URNETWORK_ROOT_JWTХранилище секретов вашего бэкендаСоздаёт провайдерские установки и привязывает их к кошельку для выплат
Адрес coldkey для выплат (ss58)Конфигурация вашего бэкендаПостоянный кошелёк для выплат: публичный, но задаёт его только бэкенд
Секрет coldkeyУ владельца, офлайнПодписывает согласия для кошелька
client.jwtПриватное состояние установкиОграниченные учётные данные установки, которые обновляет SDK

Выплаты приходят на coldkey как выплаты подсети Bittensor. Приложение читает кошелёк для выплат только для показа, и собственных настроек выплат у него нет.

Сначала согласие

При раздаче трафик других пользователей выходит через соединение пользователя, поэтому приложение должно спросить согласия до начала раздачи и позволять остановить её в любой момент. Каждый пример показывает это предупреждение дословно, а контракт закрепляет его SHA-256:

Text
Consent disclaimer: an app that integrates a URnetwork provider must collect the user's consent before it provides.
Providing shares the user's internet connection: other URnetwork users' traffic exits through the user's device and IP address.
This example starts providing without asking, because the consent screen belongs to your app.

Консольные примеры выводят его при запуске. Приложения с графическим интерфейсом и приложение для Android показывают его рядом с элементом управления, который запускает раздачу. Своего экрана согласия у примеров нет, потому что этот экран принадлежит вашему приложению, и пропускную способность они не ограничивают.

Создание провайдерских установок

Каждая установка — это провайдерская установка (provider install): клиент верхнего уровня вашей сети, который только раздаёт соединение. Ваш бэкенд создаёт её запросом POST /network/auth-client с корневым JWT, передавая описание, спецификацию устройства и флаг provide_intent, но без client_id и source_client_id:

JSON
{
  "description": "provider user:alice:laptop-1",
  "device_spec": "urnetwork-examples/go-provider",
  "provide_intent": true
}

Ответ содержит новый client_id и его ограниченный by_client_jwt, который бэкенд передаёт установке как её client.jwt. Сервер хранит флаг вместе с клиентом всё время его жизни:

  • Провайдерская установка никогда не появляется в списке пиров вашей сети (GET /network/peers и поток изменений пиров) и никогда не получает этот список.
  • Она не учитывается ни в лимите из 100 клиентов верхнего уровня, ни среди 100 недавно активных клиентов верхнего уровня, которые держат список пиров включённым.
  • Её создание не подпадает ни под этот лимит, ни под проверку одновременных клиентов вашего тарифа.

Флаг не заставляет устройство раздавать соединение. SDK сам объявляет намерение раздачи (provide intent) на своих соединениях, когда устройство раздаёт публично, и правила лимита клиентов ниже действуют для соединений, которые его объявляют. Приложениям ничего настраивать не нужно. Провайдерская установка, которая подключается без публичной раздачи, считается обычным клиентом.

Чтобы перевыпустить токен, отправьте сохранённый client_id с описанием и спецификацией устройства. При перевыпуске сервер игнорирует provide_intent, поэтому отправляйте флаг только при создании. Клиент, который не подключался 30 дней, деактивируется: на его перевыпуск сервер отвечает Client does not exist., и тогда бэкенд создаёт новую провайдерскую установку.

Два вида клиентов не являются провайдерскими установками. Собственное устройство человека, которое тоже раздаёт соединение, само объявляет намерение и остаётся пиром сети, поэтому флаг для него не ставьте. Дочерний клиент, созданный с source_client_id, никогда не предлагается другим пользователям как провайдер и ничего не зарабатывает.

Инструмент бэкенда на Go делает всё это командой wallet provision <installation-key> <client-jwt-file>, а контракт показывает те же запросы через curl.

Провайдеры и лимит клиентов

Провайдеры не подпадают под лимит одновременных клиентов вашего тарифа, пока проходят квалификацию и после того, как её прошли. Провайдер проходит квалификацию при трёх условиях:

  • публичный режим раздачи с зарегистрированными ключами раздачи (provide keys);
  • пройденная проверка состояния выхода (egress-health);
  • после 12 часов истории — надёжность за 12 часов не ниже 0.7.

Новому провайдеру даётся 1 час льготного периода на квалификацию, и этот период никогда не заканчивается раньше, чем будет предпринята хотя бы одна проверка выхода. Квалифицированный провайдер перепроверяется каждые 4 часа. У каждого клиента одна попытка квалификации раз в 8 часов.

Провайдер, не прошедший квалификацию, занимает обычное место клиента, если оно свободно. Если свободных мест нет, платформа может его отключить. Тогда SDK удерживает все соединения этого клиента с платформой на 15 минут плюс до 5 минут случайного разброса (jitter) и затем сам подключается снова. В это время приложение показывает client limit, retry at HH:MM UTC: время повторной попытки в UTC, округлённое вверх до целой минуты. Больше ничего о квалификации платформа клиенту не сообщает.

Привязка кошелька для выплат

Кошелёк для выплат — это ваш постоянный coldkey Bittensor в виде адреса ss58. Сервер платит клиенту-провайдеру только по согласию на привязку кошелька, которое подписал владелец coldkey. Согласия бывают двух видов:

СогласиеОдна подпись покрываетДля чего
Согласие сетиВсех клиентов-провайдеров вашей сети, нынешних и будущихПриложения со встроенным провайдером: владелец подписывает один раз на сеть
Согласие провайдераОдного клиента-провайдераКлиент, который зарабатывает на другой coldkey, например под управлением независимого подписанта

Для каждого клиента-провайдера и каждой эпохи побеждает действующее согласие провайдера; иначе платит согласие сети. Согласие покрывает эпохи от своего from_epoch до своего through_epoch, не более 65 536, и действует только вперёд. Чтобы сменить coldkey, подпишите новое согласие того же вида; прошлые эпохи сохраняют прежний coldkey. Отзыва без подписи не существует.

Каждое согласие проходит четыре шага, и только подпись приходит извне вашего бэкенда:

  1. Прочитайте текущую эпоху E из GET /sn/epoch.
  2. Запросите сообщение с корневым JWT: POST /sn/wallet/network-consent с coldkey и эпохами от E+1 до E+65536 или POST /sn/wallet/consent, дополнительно с client_id.
  3. Владелец coldkey подписывает точные байты сообщения в течение пяти минут.
  4. Отправьте coldkey, сообщение и подпись в POST /sn/wallet, а для согласия провайдера — ещё и тот же client_id. Проверьте привязку через GET /sn/wallet.

Подпись — это sr25519 в контексте подписи substrate над байтами сообщения в UTF-8, в исходном виде или в обёртке <Bytes>…</Bytes>. Обёрнутую форму подписывает signRaw типа bytes в расширении Polkadot. Подпись занимает 64 байта и передаётся в шестнадцатеричном виде. Сообщение согласия сети начинается с Approve URnetwork network wallet mapping, а согласия провайдера — с Approve URnetwork provider wallet mapping.

Кошельковый инструмент на Go работает с обоими видами: network-challenge и network-accept для согласия сети, challenge и accept для одного клиента и show для просмотра. Сервер без согласий сети отвечает на POST /sn/wallet/network-consent кодом 404; согласия провайдеров там по-прежнему работают. Третий, младший режим, делегирование hotkey, появится с более поздним изменением сервера и SDK. Приложения лишь помечают его как (hotkey).

Внутри приложения

Каждая установка хранит своё состояние в одном приватном каталоге: client.jwt, instance-id, identity.json и ограниченные по размеру logs/ SDK. Консольные примеры берут абсолютный путь каталога из URNETWORK_PROVIDER_STATE_DIR; приложения с графическим интерфейсом и для Android используют свои приватные данные приложения. В POSIX у каталога права 0700, у его файлов — 0600, и каждый файл заменяется атомарно. identity.json сохраняет ключи провайдера между перезапусками.

Жизненный цикл приложения короткий:

  1. Покажите предупреждение о согласии и загрузите состояние установки.
  2. Создайте Device конструктором SDK с поддержкой экстендера и ключевым материалом установки. При первом запуске его ещё нет: Device создаёт новую идентичность, которую приложение сохраняет как identity.json.
  3. Сохраняйте каждый обновлённый токен как client.jwt и останавливайтесь при выходе из аккаунта.
  4. Включите публичный режим раздачи. После этого SDK сам объявляет намерение раздачи.
  5. Читайте статус каждую секунду, а кошелёк для выплат — при запуске и каждые 10 минут.

Каждый пример показывает одни и те же четыре поля по одним и тем же правилам:

ПолеПоказывает
Статусstopped, client limit, paused, providing или starting, проверяются в этом порядке
Обслужено клиентовРазные клиенты, открывшие контракт с этим провайдером с момента запуска приложения
Передано данныхБайты, переданные для клиентов в обе стороны, в двоичных единицах
Кошелёк для выплатColdkey с его областью: (this provider), (network), (hotkey) или (another provider)

Консольные примеры выводят строку статуса, когда меняется статус, число обслуженных клиентов или кошелёк для выплат, а в остальное время раз в минуту:

Text
status: providing | clients served: 3 | data provided: 12.4 MiB | payout wallet: 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY (network)

Для providing раздача должна быть включена, а соединение с платформой установлено. Кошелёк показывает checking до первого чтения, unavailable, если это чтение не удалось, и not set, если кошелёк не привязан. Адрес выше — публичный аккаунт разработки Substrate, используемый только как пример данных. Эталонные векторы контракта точно закрепляют каждый текст.

Роль экстендера

Пока устройство раздаёт соединение, Device также выполняет роль экстендера провайдера, которая включена по умолчанию. Она слушает TCP 443, а также UDP 443 и 4053, чтобы клиенты, которые не могут напрямую достучаться до платформы, подключались через этого провайдера. UDP 53 дополнительно слушает только консольный провайдер подсети (provider provide), а клиенты пробуют и UDP 53, и 4053. Windows и macOS при первом запуске могут спросить пользователя, разрешить ли входящие соединения. В Linux процесс без права занимать эти порты продолжает раздачу без этой роли. Сборки SDK для Android и iOS её не включают.

Значение по умолчанию выключает одна настройка: DefaultProvideExtender со значением false.

БиндингПо умолчанию выключено
GoDeviceLocalSettings.DefaultProvideExtender = false или false последним аргументом конструктора
gomobileSdk.newDeviceLocalWithProvideExtender (Java, Kotlin) или SdkNewDeviceLocalWithProvideExtender (Swift) со значением по умолчанию false
C ABIurnet_new_device_local_with_provide_extender с default_provide_extender, равным false
C++urnet::newDeviceLocalWithProvideExtender со значением по умолчанию false

Собственный выбор пользователя, заданный через SetProvideExtender, по-прежнему важнее значения по умолчанию. ProvideExtenderEnabled со значением false — главный выключатель: роль не запускается никогда. Обе настройки и принимающий их конструктор есть в SDK начиная с первого релиза после sdk c638dfa8.

Работа в фоне

Консольные примеры используют три общих шаблона для работы в фоне: пользовательский сервис systemd в Linux, агент launchd в macOS и запланированную задачу в Windows. Каждый шаблон указывает каталог состояния установки, поэтому определение сервиса содержит путь, а не учётные данные. Все примеры используют одни и те же коды выхода:

Код выходаЗначение
0Запрошенная остановка, например Ctrl-C или SIGTERM
78Проблема конфигурации или учётных данных, которую перезапуск не исправит
1Любой другой сбой

Супервизоры перезапускают провайдера после сбоя, но не после 0 или 78. У launchd нет правил для отдельных кодов выхода: после 78 он повторяет попытку раз в минуту, пока состояние не исправят.

Приложения с графическим интерфейсом и для Android продолжают раздачу сами. Electron и Tauri остаются в трее, когда окно закрыто, и предлагают запуск при входе в систему. Android раздаёт соединение из сервиса переднего плана (foreground service), который запускает пользователь, и приостанавливает раздачу в сетях с тарификацией трафика.

Примеры

README каждого примера содержит шаги сборки и запуска для каждой поддерживаемой ОС и самопроверку без учётных данных. Консольные примеры работают в Windows, macOS и Linux. Провайдер на Go — эталонная реализация.

ПлатформаПримерОснова
CПровайдер на CC ABI
C++Провайдер на C++C ABI
C#Провайдер на C#C ABI через .NET
GoПровайдер на GoGo SDK
JavaПровайдер на JavaC ABI через JNA
JavaScriptПровайдер на JavaScriptNode и нативный компаньон
KotlinПровайдер на KotlinC ABI через JNA
PythonПровайдер на PythonC ABI через ctypes
RubyПровайдер на RubyC ABI через FFI
RustПровайдер на RustC ABI через крейт Rust
SwiftПровайдер на SwiftC ABI через C-модуль SwiftPM
TypeScriptПровайдер на TypeScriptNode и нативный компаньон
ElectronПровайдер на ElectronДесктопное приложение на нативном компаньоне
TauriПровайдер на TauriДесктопное приложение с крейтом Rust в том же процессе
AndroidПровайдер для AndroidAAR из gomobile в сервисе переднего плана

Доступность релиза: примерам провайдера нужен релиз SDK с API провайдера: конструктором с поддержкой экстендера, статусом лимита клиентов и областью согласия для кошелька. Они есть в SDK начиная с первого релиза после sdk c638dfa8. Пакеты описаны в руководстве по установке SDK, а остальные примеры каждого языка — в обозревателе примеров.