Desarrolladores

Integrar un proveedor

10 min de lecturaVer como Markdown ↗

Ejecuta un proveedor de URnetwork en tu app. Cada instalación comparte su conexión como cliente de tu red y cobra en una cartera de Bittensor que fijas una vez.

Integración de proveedor — tu backend aprovisiona cada instalación de proveedor para tu app en el dispositivo del usuario, la app comparte su conexión a través de URnetwork con los clientes, y el consentimiento de red del titular de la coldkey, firmado una vez sin conexión y enviado por tu backend, dirige los pagos de la subred a la coldkey de pagos fija; ninguna clave viaja dentro de la app

Un proveedor comparte la conexión a internet del dispositivo: el tráfico de otros usuarios de URnetwork sale a través de él. Mientras funciona, tu app muestra cuatro cosas: si está compartiendo la conexión, los clientes a los que sirvió, los datos que proporcionó y la cartera de pagos, solo de lectura. Los totales de ganancias vienen de tu backend. Hay ejemplos ejecutables en doce lenguajes y en tres plataformas propias: Electron, Tauri y Android. Todos implementan el contrato del proveedor, que fija las reglas exactas; esta página explica cómo encajan las piezas.

Quién guarda qué

Una integración de proveedor reparte el trabajo entre tres partes, y ninguna clave viaja dentro de una app.

  • Tu backend guarda el JWT raíz de tu red. Aprovisiona cada instalación como instalación de proveedor (provider install), entrega a cada instalación su JWT de cliente de alcance limitado y solicita los mensajes de consentimiento de la cartera.
  • El titular de la coldkey firma cada consentimiento de cartera sin conexión, con su propia herramienta de cartera, como btcli, la extensión polkadot.js o subkey. El secreto de la coldkey nunca llega a tu backend ni a la app.
  • La app ejecuta el Device local del SDK con su JWT de cliente, comparte la conexión de forma pública y muestra el estado. Nunca guarda el JWT raíz y nunca firma ni envía un consentimiento.
ValorLo guardaPara qué sirve
URNETWORK_ROOT_JWTEl almacén de secretos de tu backendAprovisiona instalaciones de proveedor y las asigna a la cartera de pagos
Dirección de la coldkey de pagos (ss58)La configuración de tu backendLa cartera de pagos fija: pública, pero solo la configura el backend
Secreto de la coldkeySu titular, sin conexiónFirma los consentimientos de la cartera
client.jwtEl estado privado de la instalaciónLa credencial de alcance limitado de la instalación, que el SDK renueva

Los pagos llegan a la coldkey como pagos de la subred de Bittensor. La app lee la cartera de pagos solo para mostrarla y no tiene ajustes de pago propios.

Primero, el consentimiento

Al compartir la conexión, el tráfico de otros usuarios sale por la del usuario, así que tu app debe pedir su consentimiento antes de empezar y debe dejarle parar en cualquier momento. Cada ejemplo muestra este aviso al pie de la letra, y el contrato fija su 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.

Los ejemplos de consola lo imprimen al arrancar. Las apps con interfaz gráfica y la de Android lo muestran junto al control que empieza a compartir la conexión. Los ejemplos no tienen pantalla de consentimiento propia, porque esa pantalla pertenece a tu app, y no limitan el ancho de banda.

Aprovisionar instalaciones de proveedor

Cada instalación es una instalación de proveedor (provider install): un cliente de nivel superior de tu red que solo comparte su conexión. Tu backend la crea con POST /network/auth-client y el JWT raíz, enviando una descripción, una especificación de dispositivo y el indicador provide_intent, pero sin client_id ni source_client_id:

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

La respuesta trae el nuevo client_id y su by_client_jwt de alcance limitado, que el backend entrega a la instalación como su client.jwt. El servidor guarda el indicador con el cliente durante toda su vida:

  • La instalación de proveedor nunca aparece en la lista de pares de tu red (GET /network/peers y el flujo de cambios de pares), y nunca recibe esa lista.
  • No cuenta para el límite de 100 clientes de nivel superior, ni para los 100 clientes de nivel superior activos recientemente que mantienen activa la lista de pares.
  • Su creación está exenta de ese límite y de la comprobación de clientes simultáneos de tu plan.

El indicador no hace que el dispositivo comparta su conexión. El SDK declara la intención de proveer (provide intent) en sus propias conexiones siempre que el dispositivo comparte su conexión de forma pública, y las reglas del límite de clientes que siguen se aplican a las conexiones que la declaran. Las apps no configuran nada para ello. Una instalación de proveedor que se conecta sin compartir su conexión de forma pública cuenta como un cliente normal.

Para volver a emitir un token, envía el client_id guardado con la descripción y la especificación de dispositivo. El servidor ignora provide_intent en una reemisión, así que envía el indicador solo al crear. Un cliente que lleva 30 días sin conectarse queda desactivado: su reemisión responde Client does not exist., y entonces el backend aprovisiona una nueva instalación de proveedor.

Hay dos tipos de cliente que no son instalaciones de proveedor. El dispositivo propio de una persona que también comparte su conexión declara la intención por sí mismo y sigue siendo un par de la red, así que no le pongas el indicador. Un cliente hijo, aprovisionado con un source_client_id, nunca se ofrece a otros usuarios como proveedor y no gana nada.

La herramienta de backend de Go hace todo esto con wallet provision <installation-key> <client-jwt-file>, y el contrato muestra las mismas peticiones con curl.

Los proveedores y el límite de clientes

Los proveedores están exentos del límite de clientes simultáneos de tu plan mientras se califican y una vez calificados. Un proveedor se califica con tres cosas:

  • el modo público de compartir conexión, con sus claves de proveedor (provide keys) registradas;
  • una sonda de salud de salida (egress-health) superada;
  • cuando ya tiene 12 horas de historial, una fiabilidad de al menos 0.7 en 12 horas.

Un proveedor nuevo tiene 1 hora de gracia para calificarse, y ese plazo nunca termina antes de que se haya intentado al menos una sonda de salida. Un proveedor calificado se vuelve a comprobar cada 4 horas. Cada cliente tiene un intento de calificación cada 8 horas.

Un proveedor que no se califica ocupa una plaza de cliente normal cuando hay una libre. Cuando no hay ninguna, la plataforma puede desconectarlo. Entonces el SDK retiene todas las conexiones de ese cliente con la plataforma durante 15 minutos más hasta 5 minutos de margen aleatorio (jitter), y vuelve a conectarse por sí mismo. Mientras tanto, la app muestra client limit, retry at HH:MM UTC: la hora del reintento en UTC, redondeada hacia arriba a un minuto entero. La plataforma no le dice nada más al cliente sobre la calificación.

Asignar la cartera de pagos

La cartera de pagos es tu coldkey fija de Bittensor, como dirección ss58. El servidor paga a un cliente proveedor solo mediante un consentimiento de asignación de cartera que firmó el titular de la coldkey. Hay dos tipos:

ConsentimientoUna firma cubreÚsalo para
Consentimiento de redTodos los clientes proveedores de tu red, los de ahora y los futurosApps que integran un proveedor: el titular firma una vez por red
Consentimiento por proveedorUn cliente proveedorUn cliente que cobra en otra coldkey, como uno que gestiona un firmante independiente

Para cada cliente proveedor y cada época, gana el consentimiento por proveedor que esté en vigor; si no hay ninguno, paga el consentimiento de red. Un consentimiento cubre las épocas desde su from_epoch hasta su through_epoch, como mucho 65.536, y solo mira hacia delante. Para cambiar la coldkey, firma un nuevo consentimiento del mismo tipo; las épocas anteriores conservan la coldkey anterior. No existe la revocación sin firma.

Cada consentimiento lleva cuatro pasos, y solo la firma viene de fuera de tu backend:

  1. Lee la época actual E en GET /sn/epoch.
  2. Pide el mensaje con el JWT raíz: POST /sn/wallet/network-consent con la coldkey y las épocas de E+1 a E+65536, o POST /sn/wallet/consent con además un client_id.
  3. El titular de la coldkey firma los bytes exactos del mensaje en menos de cinco minutos.
  4. Envía la coldkey, el mensaje y la firma a POST /sn/wallet, con el mismo client_id si es un consentimiento por proveedor. Comprueba la asignación con GET /sn/wallet.

La firma es sr25519 en el contexto de firma substrate sobre los bytes UTF-8 del mensaje, en crudo o envueltos como <Bytes>…</Bytes>. La forma envuelta es lo que firma signRaw de tipo bytes en una extensión de Polkadot. La firma ocupa 64 bytes y se envía en hexadecimal. Un mensaje de consentimiento de red empieza por Approve URnetwork network wallet mapping, y uno por proveedor, por Approve URnetwork provider wallet mapping.

La herramienta de cartera de Go gestiona los dos tipos: network-challenge y network-accept para el consentimiento de red, challenge y accept para un cliente, y show para listarlos. Un servidor sin consentimientos de red responde 404 a POST /sn/wallet/network-consent; los consentimientos por proveedor siguen funcionando en él. Un tercer modo, de menor rango, la delegación de hotkey, llegará con un cambio posterior del servidor y del SDK. Las apps solo lo etiquetan como (hotkey).

Dentro de la app

Cada instalación guarda su estado en un único directorio privado: client.jwt, instance-id, identity.json y los logs/ acotados del SDK. Los ejemplos de consola toman la ruta absoluta del directorio de URNETWORK_PROVIDER_STATE_DIR; las apps con interfaz gráfica y la de Android usan sus datos privados de aplicación. En POSIX el directorio tiene permisos 0700 y sus archivos 0600, y cada archivo se reemplaza de forma atómica. identity.json conserva las claves del proveedor entre reinicios.

El ciclo de vida de la app es corto:

  1. Muestra el aviso de consentimiento y carga el estado de la instalación.
  2. Crea el Device con el constructor del SDK que admite el extensor y con el material de claves de la instalación. En la primera ejecución no hay ninguno: el Device crea una identidad nueva, que la app guarda como identity.json.
  3. Guarda cada token renovado como client.jwt, y detente si se cierra la sesión.
  4. Pon el modo de compartir conexión en público. Entonces el SDK declara por sí mismo la intención de proveer.
  5. Lee el estado cada segundo, y la cartera de pagos al arrancar y cada 10 minutos.

Cada ejemplo muestra los mismos cuatro campos, con las mismas reglas:

CampoMuestra
Estadostopped, client limit, paused, providing o starting, comprobados en ese orden
Clientes servidosClientes distintos que abrieron un contrato con este proveedor desde que arrancó la app
Datos proporcionadosBytes retransmitidos para clientes en ambos sentidos, en unidades binarias
Cartera de pagosLa coldkey con su alcance: (this provider), (network), (hotkey) o (another provider)

Los ejemplos de consola imprimen una línea de estado cada vez que cambian el estado, el número de clientes servidos o la cartera de pagos, y si no, una vez por minuto:

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

providing exige que el proveedor esté habilitado y conectado a la plataforma. La cartera muestra checking antes de la primera lectura, unavailable si esa lectura falla y not set cuando no hay ninguna cartera asignada. La dirección de arriba es la cuenta de desarrollo pública de Substrate, usada solo como dato de ejemplo. Los vectores de referencia del contrato fijan cada texto con exactitud.

El rol de extensor

Mientras comparte la conexión, el Device también ejecuta el rol de extensor del proveedor, que está activado por defecto. Escucha en TCP 443 y en UDP 443 y 4053, para que los clientes que no pueden llegar directamente a la plataforma se conecten a través de este proveedor. Solo el proveedor de línea de comandos de la subred (provider provide) escucha también en UDP 53, y los clientes prueban tanto UDP 53 como 4053. Windows y macOS pueden pedir al usuario que permita las conexiones entrantes la primera vez. En Linux, un proceso sin privilegios para abrir estos puertos sigue compartiendo la conexión sin el rol. Las compilaciones del SDK para Android e iOS no lo incluyen.

Un solo ajuste desactiva el valor por defecto: DefaultProvideExtender en false.

BindingValor por defecto desactivado
GoDeviceLocalSettings.DefaultProvideExtender = false, o false como último argumento del constructor
gomobileSdk.newDeviceLocalWithProvideExtender (Java, Kotlin) o SdkNewDeviceLocalWithProvideExtender (Swift), con el valor por defecto en false
ABI de Curnet_new_device_local_with_provide_extender con default_provide_extender en false
C++urnet::newDeviceLocalWithProvideExtender con el valor por defecto en false

La elección del propio usuario, hecha con SetProvideExtender, sigue prevaleciendo sobre el valor por defecto. ProvideExtenderEnabled en false es el interruptor general: el rol no se ejecuta nunca. Ambos ajustes, y el constructor que los recibe, están en el SDK desde la primera versión posterior a sdk c638dfa8.

Ejecutar en segundo plano

Los ejemplos de consola comparten tres plantillas para segundo plano: un servicio de usuario de systemd en Linux, un agente de launchd en macOS y una tarea programada en Windows. Cada plantilla nombra el directorio de estado de la instalación, así que la definición del servicio contiene una ruta, nunca una credencial. Todos los ejemplos usan los mismos códigos de salida:

Código de salidaSignificado
0Una parada solicitada, como Ctrl-C o SIGTERM
78Un problema de configuración o de credenciales que un reinicio no arregla
1Cualquier otro fallo

Los supervisores reinician un proveedor tras un fallo, pero no tras 0 o 78. launchd no tiene reglas por código de salida: tras 78 sigue reintentando una vez por minuto hasta que se arregla el estado.

Las apps con interfaz gráfica y la de Android siguen compartiendo la conexión por su cuenta. Electron y Tauri se quedan en la bandeja del sistema cuando se cierra la ventana y ofrecen arrancar al iniciar sesión. Android comparte la conexión desde un servicio en primer plano que inicia el usuario, y la pausa en redes medidas.

Ejemplos

El README de cada ejemplo tiene pasos de compilación y ejecución para cada sistema operativo que admite y una autoprueba sin credenciales. Los ejemplos de consola funcionan en Windows, macOS y Linux. El proveedor en Go es la implementación de referencia.

PlataformaEjemploConstruido sobre
CProveedor en CLa ABI de C
C++Proveedor en C++La ABI de C
C#Proveedor en C#La ABI de C, a través de .NET
GoProveedor en GoEl SDK de Go
JavaProveedor en JavaLa ABI de C, a través de JNA
JavaScriptProveedor en JavaScriptNode y el companion nativo
KotlinProveedor en KotlinLa ABI de C, a través de JNA
PythonProveedor en PythonLa ABI de C, a través de ctypes
RubyProveedor en RubyLa ABI de C, a través de FFI
RustProveedor en RustLa ABI de C, a través del crate de Rust
SwiftProveedor en SwiftLa ABI de C, a través de un módulo C de SwiftPM
TypeScriptProveedor en TypeScriptNode y el companion nativo
ElectronProveedor en ElectronUna app de escritorio sobre el companion nativo
TauriProveedor en TauriUna app de escritorio con el crate de Rust en el mismo proceso
AndroidProveedor en AndroidEl AAR de gomobile, en un servicio en primer plano

Disponibilidad de la versión: los ejemplos de proveedor necesitan una versión del SDK con las API de proveedor: el constructor que admite el extensor, el estado del límite de clientes y el alcance del consentimiento de la cartera. Están en el SDK desde la primera versión posterior a sdk c638dfa8. Instalar el SDK explica los paquetes, y el explorador de ejemplos tiene los demás ejemplos de cada lenguaje.