开发者

集成提供者

10 分钟阅读以 Markdown 格式查看 ↗

在你的应用中运行 URnetwork 提供者。每个安装都作为你网络中的客户端共享连接,收益进入你只需设定一次的同一个 Bittensor 钱包。

提供者集成 — 你的后端为用户设备上的你的应用开通每个提供者安装,应用通过 URnetwork 向客户端共享连接,而 coldkey 持有者的网络同意(离线签署一次、由你的后端提交)把子网收益导向固定的收款 coldkey;任何密钥都不会随应用分发

提供者共享设备的互联网连接:其他 URnetwork 用户的流量经由它出网。运行期间,你的应用显示四项内容:是否正在共享连接、服务过的客户端、提供的数据量,以及只读的收款钱包。收益总额来自你的后端。可运行的示例覆盖十二种语言和三个独立平台:Electron、Tauri 和 Android。它们都实现了提供者契约,确切的规则都在其中;本页解释各个部分如何配合。

谁持有什么

提供者集成把工作分给三方,任何密钥都不会随应用分发。

  • 你的后端持有你网络的根 JWT。它把每个安装开通为提供者安装(provider install),把受限的客户端 JWT 交付给每个安装,并请求钱包同意消息。
  • coldkey 持有者用自己的钱包工具离线签署每一份钱包同意,例如 btcli、polkadot.js 扩展或 subkey。coldkey 私钥永远不会到达你的后端或应用。
  • 应用用自己的客户端 JWT 运行 SDK 的本地 Device,公开共享连接并显示状态。它从不持有根 JWT,也从不签署或提交同意。
值保存在用途
URNETWORK_ROOT_JWT你后端的密钥存储开通提供者安装,并把它们映射到收款钱包
收款 coldkey 地址(ss58)你后端的配置固定的收款钱包:公开,但只由后端设定
coldkey 私钥其持有者,离线保存签署钱包同意
client.jwt安装的私有状态该安装的受限凭证,由 SDK 刷新

收款以 Bittensor 子网收益的形式支付给 coldkey。应用读取收款钱包只为显示,自身没有任何收款设置。

先取得同意

共享连接时,其他用户的流量从用户的连接出网,所以你的应用必须在开始前征得同意,并允许用户随时停止。每个示例都原样显示这段免责声明,契约固定了它的 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.

控制台示例在启动时打印它。GUI 应用和 Android 应用把它显示在开始共享的控件旁边。示例本身没有同意界面,因为那个界面属于你的应用;它们也不限制带宽。

开通提供者安装

每个安装都是一个提供者安装(provider install):你网络中只共享连接的顶层客户端。你的后端用根 JWT 调用 POST /network/auth-client 来创建它,发送描述、设备规格和 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 表示的重试时间,向上取整到整分钟。除此之外,平台不会告诉客户端任何关于资格的信息。

映射收款钱包

收款钱包是你固定的 Bittensor coldkey,以 ss58 地址表示。服务器只通过 coldkey 持有者签署的钱包映射同意向提供者客户端付款。同意分两种:

同意一次签名覆盖适用于
网络同意你网络中现在和将来的所有提供者客户端嵌入提供者的应用:持有者每个网络只签一次
单个提供者同意一个提供者客户端收益进入另一个 coldkey 的客户端,例如由独立签名者运营的客户端

对每个提供者客户端的每个纪元,生效中的单个提供者同意优先;否则由网络同意付款。一份同意覆盖从它的 from_epoch 到 through_epoch 的纪元,最多 65,536 个,并且只向前生效。要更换 coldkey,就签一份同类的新同意;之前的纪元保留之前的 coldkey。不存在不经签名的撤销。

每份同意需要四步,只有签名来自你的后端之外:

  1. 从 GET /sn/epoch 读取当前纪元 E。
  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 检查映射。

签名是 substrate 签名上下文中针对消息 UTF-8 字节的 sr25519 签名,可以是原始字节,也可以包装成 <Bytes>…</Bytes>。包装形式就是 Polkadot 扩展的 signRaw(类型为 bytes)所签的内容。签名为 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,以及 SDK 有大小上限的 logs/。控制台示例从 URNETWORK_PROVIDER_STATE_DIR 读取该目录的绝对路径;GUI 应用和 Android 应用使用各自的私有应用数据。在 POSIX 上,该目录权限为 0700,其中的文件为 0600,每个文件都以原子方式替换。identity.json 让提供者的密钥在重启后保持不变。

应用的生命周期很短:

  1. 显示同意免责声明,并加载安装状态。
  2. 用 SDK 支持扩展器的构造函数和安装的密钥材料创建 Device。首次运行时还没有密钥材料: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,让无法直接连到平台的客户端可以经由这个提供者连接。只有子网的命令行提供者(provider provide)还会监听 UDP 53,客户端则会同时尝试 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 c638dfa8 之后的第一个发布版起出现在 SDK 中。

在后台运行

控制台示例共用三份后台运行模板:Linux 上的 systemd 用户服务、macOS 上的 launchd 代理,以及 Windows 上的计划任务。每份模板都指定安装的状态目录,所以服务定义里只有路径,从不包含凭证。所有示例都使用相同的退出码:

退出码含义
0请求的停止,例如 Ctrl-C 或 SIGTERM
78重启也无法修复的配置或凭证问题
1其他任何失败

监管程序会在失败后重启提供者,但在 0 或 78 之后不会。launchd 没有按退出码区分的规则:在 78 之后,它会每分钟重试一次,直到状态被修复。

GUI 应用和 Android 应用会自行持续共享。Electron 和 Tauri 在窗口关闭后留在系统托盘中,并提供登录时启动。Android 在用户启动的前台服务中共享连接,并在按流量计费的网络上暂停共享。

示例

每个示例的 README 都包含其所支持的每个操作系统的构建和运行步骤,以及无需凭证的自检。控制台示例可在 Windows、macOS 和 Linux 上运行。Go 提供者是参考实现。

平台示例构建基础
CC 提供者C ABI
C++C++ 提供者C ABI
C#C# 提供者通过 .NET 使用 C ABI
GoGo 提供者Go SDK
JavaJava 提供者通过 JNA 使用 C ABI
JavaScriptJavaScript 提供者Node 与原生 companion
KotlinKotlin 提供者通过 JNA 使用 C ABI
PythonPython 提供者通过 ctypes 使用 C ABI
RubyRuby 提供者通过 FFI 使用 C ABI
RustRust 提供者通过 Rust crate 使用 C ABI
SwiftSwift 提供者通过 SwiftPM C 模块使用 C ABI
TypeScriptTypeScript 提供者Node 与原生 companion
ElectronElectron 提供者基于原生 companion 的桌面应用
TauriTauri 提供者在同一进程中使用 Rust crate 的桌面应用
AndroidAndroid 提供者在前台服务中运行的 gomobile AAR

发布状态:提供者示例需要一个包含提供者 API 的 SDK 发布版:支持扩展器的构造函数、客户端上限状态,以及钱包同意的范围。它们从 sdk c638dfa8 之后的第一个发布版起出现在 SDK 中。安装 SDK 介绍各个包,示例浏览页收录每种语言的其他示例。