Bittensor 子网超参数是子网激励机制的管理参数,包括 tempo、免疫期、最大验证者数量、难度、权重速率限制以及注册设置。它们是链上状态而非常量:治理和子网所有者可以在任意区块修改它们,而且每个子网各不相同,因此客户端必须读取它们,而不能硬编码。首选的读取路径是 runtime API subnetInfo_getSubnetHyperparams,它返回一个单一的类型化结构体,其组合方式与 runtime 自身组合方式一致。通过 state_getStorage 直接读取存储也是可行的,但需要精确的存储键哈希和 SCALE 解码,而且原始键在 runtime 升级后并不稳定。本指南展示如何正确读取、解码、交叉校验和缓存超参数,并附有可运行的 Node.js 示例和用于对照自己端点进行测量的结果表。
什么是 Bittensor 子网超参数
子网超参数是子网激励机制的管理参数之一。该集合包括 tempo(子网一个 epoch 的区块数)、免疫期(新注册神经元在多长时间内受到保护而不会被注销)、最大验证者数量(验证者许可上限)、最小和最大难度、权重速率限制(设置权重的频率),以及注册设置,如注册成本和允许注册的区块。Bittensor 文档在 subnet hyperparameters 维护了权威参考。
这些值并非协议中的常量。它们是链上状态:治理可以修改它们,子网所有者也可以通过相关 extrinsic 调整子网特定参数。它们还因每个子网而异,因此为一个 netuid 读取的值并不能说明另一个子网的情况。硬编码 tempo 或最大验证者数量的客户端,会在参数第一次发生变化时悄然失去正确性。
由于超参数是状态,正确的客户端行为是在已知区块从链上读取它们,并且只将任何缓存副本视为对该区块有效。本指南的其余部分将介绍两种读取路径、如何解码结果,以及如何保持缓存真实可靠。
- Tempo:子网激励机制每个 epoch 的区块数。
- 免疫期:新神经元受到保护而不会被注销的区块数。
- 最大验证者数量:子网的验证者许可上限。
- 最小/最大难度:注册的工作量证明难度边界。
- 权重速率限制:设置权重操作之间的最小区块间隔。
- 注册设置:成本及相关注册约束。
为什么超参数必须读取,而不能硬编码
假设 tempo 固定的客户端,一旦治理修改了它,就会错误计算 epoch 边界。假设最大验证者数量固定的客户端,会错误报告验证者容量。免疫期、难度边界和权重速率限制同样如此。这些都是可以在任意区块发生变化的实时值。
实际后果是,任何下游计算——epoch 时间、验证者资格、注册成本估算——都应基于在已知区块读取的超参数,而不是源代码中的常量。如果你需要同时获取 epoch 和 emission 上下文,Bittensor 子网 tempo、epoch 和 emission 读取 指南详细介绍了这些读取。
读取而非硬编码还能让你的客户端跨子网保持弹性。一条为给定 netuid 读取超参数的代码路径适用于所有子网,而每个子网的常量会成倍增加维护工作和漂移风险。
Runtime API 读取路径:subnetInfo_getSubnetHyperparams
首选的读取路径是 runtime API subnetInfo_getSubnetHyperparams,它返回一个包含子网超参数的单一类型化结构体。由于 runtime 组合该结构体,字段的组装方式与 runtime 自身使用它们的方式一致,从而消除了错误哈希存储键或错误解码 SCALE 值的风险。
某些 subtensor 版本会暴露该方法的 V2 变体。方法名称和版本因 subtensor 版本而异,因此请将确切名称视为“以文档为准 / 因节点而异”,并针对你正在查询的节点进行确认。Substrate state_getMetadata 与 runtime 版本 指南说明了如何检查 runtime 元数据,以发现节点暴露了哪些 runtime API 方法。
当你需要可复现性时,请在显式区块调用 runtime API。传入区块哈希会将读取固定到该状态,这正是缓存和与 metagraph 快照交叉校验时所需要的。
- 返回单一类型化结构体,而非原始存储字节。
- 字段组合与 runtime 自身使用方式一致。
- 方法名称和版本因 subtensor 版本而异。
- 固定到区块哈希以实现可复现读取。
直接存储读取路径:state_getStorage
另一种方法是使用 state_getStorage 直接读取 SubtensorModule 存储映射。这需要计算正确的存储键,在 Substrate 中,存储键由 pallet 名称、存储项名称和键参数派生,然后用适当的哈希器(twox 或 blake2,取决于映射)进行哈希。任何部分出错都会返回 null 或垃圾数据。
即使键正确,返回的值也是 SCALE 编码的,必须根据 runtime 元数据中定义的类型进行解码。该类型可能随 runtime 升级而变化,这使得原始存储键和解码器随时间变得不稳定。因此,为了正确性,runtime API 路径更可取,直接存储读取最好保留给没有 runtime API 暴露所需值的情况。
如果你确实直接读取存储,请将读取固定到某个区块,并将 runtime 版本与值一起记录,以便检测解码器何时需要更新。
- 需要精确的存储键哈希(twox/blake2)和 SCALE 解码。
- 键布局和值类型可能随 runtime 升级而变化。
- 键错误或值不存在时返回 null。
- 存在 runtime API 时优先使用它。
使用 subnetInfo_getSubnetInfo 组装完整子网记录
超参数只是子网状态的一部分。要组装完整的子网记录,请将超参数与 subnetInfo_getSubnetInfo 结合,后者返回所有者、emission 和注册成本等字段。两者结合,你就能获得管理机制的参数以及其周围的经济上下文。
关于验证者和质押上下文,通过 RPC 读取 Bittensor 质押状态 指南涵盖了委托和质押读取,读取 Bittensor metagraph 状态 涵盖了 metagraph。完整的子网视图通常会在同一区块连接超参数、子网信息和 metagraph 快照。
在同一区块连接读取很重要。如果你在区块 N 读取超参数,在区块 N+1 读取 metagraph,期间参数变化可能导致两者不一致。请将两次读取都固定到同一区块哈希。
解码返回的超参数结构体
subnetInfo_getSubnetHyperparams 返回的结构体包含子网的管理字段。根据类型解码每个字段:tempo 和免疫期是区块数,最大验证者数量是计数,难度边界是数值,权重速率限制是区块间隔。注册设置通常是数值或布尔值,取决于字段。
某些字段是子网特定的,因此字段缺失不一定是错误。子网可能不使用每个参数,runtime 版本也可能添加或重命名字段。将缺失字段视为“不适用或此节点未暴露”,而不是解码失败,并将 runtime 版本与解码后的结构体一起记录。
由于结构体是类型化的,你可以使用 runtime 元数据解码它,而不必手工编写解码器。这是 runtime API 路径相对于原始存储读取的主要正确性优势。
- Tempo 和免疫期:区块数。
- 最大验证者数量:验证者许可计数。
- 最小/最大难度:数值边界。
- 权重速率限制:区块间隔。
- 缺失字段可能是子网特定的,而非错误。
可运行的 Node.js 示例:通过 WebSocket 读取超参数
下面的示例连接到 Subtensor WebSocket 端点,调用 runtime API 获取子网超参数,打印结构体字段,并在新区块后重新读取。请将端点和 netuid 替换为你自己的值。端点因提供商而异;有关端点选项,请参阅 Bittensor RPC 指南(RPC Assistant),有关网络上下文,请参阅 Bittensor Finney 网络页面。
该脚本使用基于 WebSocket 的最小 JSON-RPC 2.0 客户端。它将读取固定到最新区块哈希,使值可复现,并订阅新区块以触发重新读取。这是长时间运行服务中应使用的模式。
const WebSocket = require('ws');
const ENDPOINT = 'wss://your-subtensor-endpoint';
const NETUID = 1;
let id = 0;
function call(ws, method, params) {
return new Promise((resolve, reject) => {
const reqId = ++id;
const onMsg = (data) => {
const msg = JSON.parse(data);
if (msg.id !== reqId) return;
ws.off('message', onMsg);
if (msg.error) reject(new Error(JSON.stringify(msg.error)));
else resolve(msg.result);
};
ws.on('message', onMsg);
ws.send(JSON.stringify({ jsonrpc: '2.0', id: reqId, method, params }));
});
}
async function readHyperparams(ws) {
const hash = await call(ws, 'chain_getBlockHash', []);
const params = await call(ws, 'state_call', [
'SubnetInfoRuntimeApi_get_subnet_hyperparams',
'0x' + NETUID.toString(16).padStart(8, '0'),
hash
]);
console.log('block', hash);
console.log('hyperparams (SCALE hex)', params);
return { hash, params };
}
(async () => {
const ws = new WebSocket(ENDPOINT);
await new Promise((r) => ws.on('open', r));
await readHyperparams(ws);
await call(ws, 'chain_subscribeNewHeads', []);
ws.on('message', async (data) => {
const msg = JSON.parse(data);
if (msg.method === 'chain_newHead') {
await readHyperparams(ws);
}
});
})();检测过期缓存并按计划重新读取
缓存的超参数值仅在其读取时的区块有效。要检测过期,请将区块哈希和 runtime 版本与值一起存储,并按计划或在新区块订阅时重新读取。如果 runtime 版本发生变化,请使缓存失效并重新解码。
一个实用的模式是每 N 个区块重新读取超参数(其中 N 相对于 tempo 较小),并在 runtime 版本变化时强制重新读取。对于 tempo 较短的子网,较短的重新读取间隔是合适的;对于较长的 tempo,较长的间隔也可以。关键是要限制最大过期时间,而不是假设值永远不会改变。
如果你向其他服务提供超参数,请将区块哈希与值一起暴露,以便消费者决定该值对其用例是否足够新鲜。
- 将区块哈希和 runtime 版本与值一起存储。
- 按计划或在新区块订阅时重新读取。
- 当 runtime 版本变化时使缓存失效。
- 将区块哈希暴露给下游消费者。
将超参数与 Metagraph 交叉校验
一个有用的交叉校验是验证 metagraph 中活跃验证者的数量是否符合 max_validators。在与超参数相同的区块读取 metagraph 并统计验证者许可。如果计数超过 max_validators,要么你的读取已过期,要么你统计了错误的字段。
同样的方法适用于免疫期:在免疫窗口内注册的神经元不应被注销。与 metagraph 交叉校验可以捕获单次读取可能遗漏的解码错误和过期缓存。读取 Bittensor metagraph 状态 指南涵盖了你需要的 metagraph 字段。
交叉校验不能替代正确的解码,但它们是检测运行中服务漂移的廉价方法。
结果表:对照你自己的端点进行测量
由于端点行为和 runtime 版本各不相同,请对照你自己的端点进行测量,而不是依赖已发布的数字。下表是一个模板,供你填入自己的观察结果。记录每次读取的端点、区块哈希、runtime 版本和解码后的字段。
在多个区块运行读取并进行比较。如果某个字段在 runtime 版本未变化的情况下发生变化,那是治理或子网所有者的更改,你的缓存本应捕获它。如果某个字段解码失败,请记录 runtime 版本并检查元数据中的当前类型。
- 端点:你的 Subtensor WebSocket 或 HTTP URL。
- 区块哈希:读取所固定的区块。
- Runtime 版本:来自 state_getRuntimeVersion。
- Tempo、免疫期、最大验证者数量:解码后的值。
- 权重速率限制、最小/最大难度:解码后的值。
- 交叉校验:活跃验证者数量与最大验证者数量。
限制、权衡与故障排除
超参数是治理和子网所有者可以在任意区块更改的链上状态。runtime API 方法名称和版本因 subtensor 版本而异,因此请将确切名称视为“以文档为准 / 因节点而异”,并针对你的端点进行确认。原始存储键在 runtime 升级后不稳定,而且某些字段是子网特定的,因此字段缺失不一定是错误。
常见故障模式:state_getStorage 返回 null 通常意味着存储键错误或值不存在;解码失败通常意味着 runtime 类型已更改;值过期通常意味着缓存未在 runtime 版本变化时失效。有关端点和连接问题,请参阅 Bittensor RPC 指南(RPC Assistant)。
对于生产服务,优先使用 runtime API 路径,将读取固定到区块,存储 runtime 版本,并与 metagraph 交叉校验。如果你需要托管端点,API 服务 和 RPC 定价 页面描述了相关选项,OnFinality Learn 中心 收集了相关指南。
- 方法名称/版本因节点而异:请对照元数据确认。
- 原始存储键在 runtime 升级后不稳定。
- 缺失字段可能是子网特定的,而非错误。
- 存储结果为 null:键错误或值不存在。
- 解码失败:runtime 类型已更改。
- 值过期:缓存未在版本变化时失效。
构建超参数感知客户端的后续步骤
首先在固定区块为单个 netuid 读取超参数,然后使用 runtime 元数据解码结构体。添加新区块订阅,并在有界间隔内重新读取。将区块哈希和 runtime 版本与每个值一起存储,并将它们暴露给消费者。
一旦读取路径稳定,将其与 subnetInfo_getSubnetInfo 以及同一区块的 metagraph 快照连接,以组装完整的子网记录。使用上述交叉校验来检测漂移。有关相关读取,请参阅 Bittensor 子网 tempo、epoch 和 emission 读取 和 通过 RPC 读取 Bittensor 质押状态 指南。
最后,将超参数读取视为版本化契约:当 runtime 版本变化时,重新验证你的解码器和缓存失效逻辑。这种纪律是客户端随网络演进保持正确性的关键。