集成提供者
在你的应用中运行 URnetwork 提供者。每个安装都作为你网络中的客户端共享连接,收益进入你只需设定一次的同一个 Bittensor 钱包。
提供者共享设备的互联网连接:其他 URnetwork 用户的流量经由它出网。运行期间,你的应用显示四项内容:是否正在共享连接、服务过的客户端、提供的数据量,以及只读的收款钱包。收益总额来自你的后端。可运行的示例覆盖十二种语言和三个独立平台:Electron、Tauri 和 Android。它们都实现了提供者契约,确切的规则都在其中;本页解释各个部分如何配合。
谁持有什么
提供者集成把工作分给三方,任何密钥都不会随应用分发。
- 你的后端持有你网络的根 JWT。它把每个安装开通为提供者安装(provider install),把受限的客户端 JWT 交付给每个安装,并请求钱包同意消息。
- coldkey 持有者用自己的钱包工具离线签署每一份钱包同意,例如 btcli、polkadot.js 扩展或 subkey。coldkey 私钥永远不会到达你的后端或应用。
- 应用用自己的客户端 JWT 运行 SDK 的本地 Device,公开共享连接并显示状态。它从不持有根 JWT,也从不签署或提交同意。
| 值 | 保存在 | 用途 |
|---|---|---|
URNETWORK_ | 你后端的密钥存储 | 开通提供者安装,并把它们映射到收款钱包 |
| 收款 coldkey 地址(ss58) | 你后端的配置 | 固定的收款钱包:公开,但只由后端设定 |
| coldkey 私钥 | 其持有者,离线保存 | 签署钱包同意 |
client.jwt | 安装的私有状态 | 该安装的受限凭证,由 SDK 刷新 |
收款以 Bittensor 子网收益的形式支付给 coldkey。应用读取收款钱包只为显示,自身没有任何收款设置。
先取得同意
共享连接时,其他用户的流量从用户的连接出网,所以你的应用必须在开始前征得同意,并允许用户随时停止。每个示例都原样显示这段免责声明,契约固定了它的 SHA-256:
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/ 来创建它,发送描述、设备规格和 provide_ 标志,但不带 client_id 或 source_:
{
"description": "provider user:alice:laptop-1",
"device_spec": "urnetwork-examples/ go-provider",
"provide_intent": true
}响应带有新的 client_id 及其受限的 by_,后端把它作为 client.jwt 交付给该安装。服务器在客户端的整个生命周期内保存这个标志:
- 提供者安装永远不会出现在你网络的对等节点列表中(
GET /network/和对等节点变更流),也永远收不到这份列表。peers - 它不计入 100 个顶层客户端的上限,也不计入维持对等节点列表开启的 100 个近期活跃顶层客户端。
- 它的创建不受该上限和你套餐的并发客户端检查约束。
这个标志不会让设备开始共享。只要设备在公开共享,SDK 就会自行在自己的连接上声明提供意图(provide intent),下文的客户端上限规则适用于声明了它的连接。应用无需为此设置任何东西。没有公开共享就连接的提供者安装,按普通客户端计算。
要重新签发令牌,就发送保存的 client_id 以及描述和设备规格。重新签发时服务器会忽略 provide_,所以只在创建时发送这个标志。连续 30 天没有连接的客户端会被停用:它的重新签发请求得到 Client does not exist.,此时后端应开通一个新的提供者安装。
有两类客户端不是提供者安装。个人自己的、同时也在共享的设备会自行声明意图,并保持为网络的对等节点,所以不要为它设置这个标志。用 source_ 开通的子客户端永远不会作为提供者提供给其他用户,也赚不到任何收益。
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_ 的纪元,最多 65,536 个,并且只向前生效。要更换 coldkey,就签一份同类的新同意;之前的纪元保留之前的 coldkey。不存在不经签名的撤销。
每份同意需要四步,只有签名来自你的后端之外:
- 从
GET /sn/epoch读取当前纪元E。 - 用根 JWT 请求消息:
POST /sn/,带上 coldkey 和从wallet/ network-consent E+1到E+65536的纪元;或者POST /sn/,另外带上wallet/ consent client_id。 - coldkey 持有者在五分钟内签署消息的确切字节。
- 把 coldkey、消息和签名提交到
POST /sn/wallet,单个提供者同意还要带上同一个client_id。用GET /sn/wallet检查映射。
签名是 substrate 签名上下文中针对消息 UTF-8 字节的 sr25519 签名,可以是原始字节,也可以包装成 <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/ 返回 404;单个提供者同意在那里仍然有效。第三种更低一级的模式,即 hotkey 委托,将随后续的服务器和 SDK 变更推出。应用只需把它标记为 (hotkey)。
应用内部
每个安装把状态保存在一个私有目录中:client.jwt、instance-id、identity.json,以及 SDK 有大小上限的 logs/。控制台示例从 URNETWORK_ 读取该目录的绝对路径;GUI 应用和 Android 应用使用各自的私有应用数据。在 POSIX 上,该目录权限为 0700,其中的文件为 0600,每个文件都以原子方式替换。identity.json 让提供者的密钥在重启后保持不变。
应用的生命周期很短:
- 显示同意免责声明,并加载安装状态。
- 用 SDK 支持扩展器的构造函数和安装的密钥材料创建 Device。首次运行时还没有密钥材料:Device 会生成新的身份,应用将其保存为
identity.json。 - 把每个刷新后的令牌保存为
client.jwt,并在登出时停止。 - 把共享模式设为公开。之后 SDK 会自行声明提供意图。
- 每秒读取一次状态,并在启动时以及每 10 分钟读取一次收款钱包。
每个示例都按相同的规则显示相同的四个字段:
| 字段 | 显示内容 |
|---|---|
| 状态 | stopped、client limit、paused、providing 或 starting,按此顺序判断 |
| 服务过的客户端 | 自应用启动以来,与该提供者建立过合同的不同客户端 |
| 提供的数据 | 双向为客户端转发的字节数,使用二进制单位 |
| 收款钱包 | coldkey 及其范围:(this provider)、(network)、(hotkey) 或 (another provider) |
每当状态、服务过的客户端数或收款钱包发生变化,控制台示例就打印一行状态,否则每分钟打印一次:
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。
| 绑定 | 默认关闭 |
|---|---|
| Go | DeviceLocalSettings.,或把构造函数的最后一个参数设为 false |
| gomobile | Sdk.(Java、Kotlin)或 SdkNewDeviceLocalWithProvideExtender(Swift),默认值设为 false |
| C ABI | urnet_,default_ 设为 false |
| C++ | urnet::,默认值设为 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 提供者是参考实现。
| 平台 | 示例 | 构建基础 |
|---|---|---|
| C | C 提供者 | C ABI |
| C++ | C++ 提供者 | C ABI |
| C# | C# 提供者 | 通过 .NET 使用 C ABI |
| Go | Go 提供者 | Go SDK |
| Java | Java 提供者 | 通过 JNA 使用 C ABI |
| JavaScript | JavaScript 提供者 | Node 与原生 companion |
| Kotlin | Kotlin 提供者 | 通过 JNA 使用 C ABI |
| Python | Python 提供者 | 通过 ctypes 使用 C ABI |
| Ruby | Ruby 提供者 | 通过 FFI 使用 C ABI |
| Rust | Rust 提供者 | 通过 Rust crate 使用 C ABI |
| Swift | Swift 提供者 | 通过 SwiftPM C 模块使用 C ABI |
| TypeScript | TypeScript 提供者 | Node 与原生 companion |
| Electron | Electron 提供者 | 基于原生 companion 的桌面应用 |
| Tauri | Tauri 提供者 | 在同一进程中使用 Rust crate 的桌面应用 |
| Android | Android 提供者 | 在前台服务中运行的 gomobile AAR |
发布状态:提供者示例需要一个包含提供者 API 的 SDK 发布版:支持扩展器的构造函数、客户端上限状态,以及钱包同意的范围。它们从 sdk c638dfa8 之后的第一个发布版起出现在 SDK 中。安装 SDK 介绍各个包,示例浏览页收录每种语言的其他示例。