Substrate Runtime:面向AI Agent的可信执行内核
1. Substrate 不是“另一个区块链框架”它本质是一套可组合的运行时构建范式很多人第一次听说 Substrate是在 Polkadot 生态里——“Polkadot 的底层技术栈”“波卡的 SDK”。这种说法没错但严重窄化了它的实际定位。Substrate 的核心价值从来不是“帮你快速发一条链”而是提供一套将区块链逻辑从共识、网络、存储等基础设施中彻底解耦出来并以模块化、可复用、可热更新方式组织运行时Runtime的工程范式。这听起来很抽象但换一个生活化的类比就立刻清晰Substrate 就像 Linux 内核提供的Kbuild Kconfig Module Load/Unload机制——你不用重写整个内核就能通过配置选项Kconfig、编译模块ko 文件、动态加载insmod来定制一个满足特定场景的系统镜像。Substrate 的 Runtime 就是这个“可加载模块”而 FRAMEFramework for Runtime Aggregation of Modularized Entities就是它的 Kbuild 系统。关键词 “substrate” 在当前技术语境下已远超 Polkadot 生态专属标签。它正被越来越多与agent、OCI、Kubernetes、gVisor等技术栈交叉使用的团队重新审视。为什么因为这些领域共同面临一个底层挑战如何在强隔离、高可控、可声明式管理的环境中安全、高效地执行用户定义的、可能具备状态和长期生命周期的逻辑单元即 agent。传统方案要么太重完整 VM要么太弱无状态函数而 Substrate 提供了一条中间路径它把“逻辑执行环境”本身变成一个可编程、可验证、可升级的“轻量级可信执行单元”。这不是在造新轮子而是在重新定义“执行单元”的抽象层级——把 runtime 从“黑盒二进制”提升为“可审计、可组合、可演化的一等公民”。这直接解释了为什么 “substrate” 会和 “agent”、“OCI”、“kubernetes” 出现在同一搜索热词池里。OCIOpen Container Initiative规范定义了容器镜像的格式与运行时接口Kubernetes 是容器编排的事实标准gVisor 是一种用户态内核为容器提供更强隔离而 agent在 AI 工程语境下正演变为一种具备记忆、工具调用、规划能力的长期运行服务实体。它们共同指向一个趋势未来的服务架构将由大量细粒度、有状态、可独立升级、带策略约束的“智能执行体”组成而非单体应用或无状态微服务。Substrate 的 Runtime 模块恰好天然适配这一范式——每个 FRAME pallet模块就是一个封装了状态、逻辑、事件、错误的“agent 基础能力单元”而 Runtime 的 wasm 执行环境则提供了沙箱化、确定性、可验证的执行保障。它不替代 Kubernetes而是为 Kubernetes 上运行的 agent 提供更底层、更可靠的“逻辑内核”。提示不要把 Substrate 当成“区块链版 Django”。它的设计哲学更接近 Rust 的std::synctokio::taskserde组合——不是给你一堆开箱即用的 Web 功能而是给你一套构建高可靠性、高并发、强类型、可组合系统原语的工具集。理解这一点是避免后续所有踩坑的第一步。2. Runtime 模块化FRAME 的真实工作流与 pallet 设计心法Substrate 的灵魂在于 FRAMEFramework for Runtime Aggregation of Modularized Entities但它绝非一个“拖拽式模块库”。FRAME 的核心机制是通过 Rust 的宏系统decl_module!,decl_storage!,decl_event!和 trait 系统在编译期将多个 pallet模块的存储项、调用逻辑、事件定义、错误枚举静态聚合成一个统一的 Runtime 类型。这个过程不是运行时拼接而是 Rust 编译器参与的深度代码生成。这意味着pallet 之间不是松散耦合的插件而是编译期强绑定的组件。理解这一点是写出健壮 pallet 的前提。我们以一个典型的 “Agent Registry” pallet 为例说明其设计逻辑。假设我们要在链上注册并管理 AI agent 的元信息名称、版本、入口地址、资源配额、健康检查端点。这个 pallet 需要存储 agent 的元数据StorageMapAgentId, AgentInfo提供register_agent和deregister_agent调用发出AgentRegistered和AgentDeregistered事件定义Error::AgentAlreadyExists等错误在 FRAME 中这并非简单地写几个函数。关键在于Configtrait 的设计。每个 pallet 都必须实现一个Configtrait它定义了该 pallet 所依赖的其他 pallet 的接口。例如AgentRegistry可能需要访问Systempallet 获取调用者Origin需要Balancespallet 扣除注册费甚至需要Schedulerpallet 来安排定期健康检查。这些依赖不是通过参数传递而是通过Configtrait 的关联类型associated types在编译期声明pub trait Config: frame_system::Config pallet_balances::Config { type Event: FromEventSelf IsTypeSelf as frame_system::Config::Event; type Currency: ReservableCurrencySelf::AccountId; type Scheduler: ScheduleNamedSelf::BlockNumber, Self::Call, Self::Origin; }这段代码的含义是任何想使用AgentRegistry的 Runtime都必须在其Config实现中同时实现frame_system::Config和pallet_balances::Config并且提供Scheduler的具体类型。这强制了依赖关系的显式声明和类型安全。如果某个 Runtime 忘记实现pallet_balances::Config编译器会直接报错而不是在运行时崩溃。实操中我踩过最深的坑就是试图绕过Configtrait用OptionT或Boxdyn Trait来做“可选依赖”。这是完全违背 FRAME 设计哲学的。FRAME 的理念是“编译期确定一切”所有依赖、存储布局、调用路由都在build.rs运行construct_runtime!宏时固化。construct_runtime!宏会遍历所有 pallet收集它们的Storage、Call、Event、Error并生成最终的Runtime结构体。这个过程会进行严格的类型检查和冲突检测比如两个 pallet 不能定义同名的 storage item。因此pallet 设计的心法是最小化依赖只声明真正需要的Config关联类型避免“为了方便”而引入不必要的 pallet。明确所有权StorageMap的 key 类型必须是EncodeDecodeClonePartialEqvalue 类型同理。Rust 的类型系统在这里是你的第一道防线。事件即契约Event枚举是 pallet 对外的唯一“通信协议”。下游服务如 indexer 或 agent 监控器只应监听Event而不应直接读取 storage。因为 storage 的内部结构可能随 pallet 升级而改变但Event的序列化格式是向后兼容的。错误即文档Error枚举的每个 variant 都应有清晰的注释说明触发条件。这不仅是给开发者看的更是给链上治理提案者看的——当一个错误发生时它直接对应着一个可被投票修正的治理动作。注意decl_module!宏在 Substrate 3.0 版本中已被弃用取而代之的是#[pallet::call]等属性宏。但其背后的设计思想——编译期聚合、trait 约束、类型安全——丝毫未变。迁移到新宏时不要只改语法更要理解其背后的范式迁移。3. WASM Runtime不只是沙箱它是 agent 的“确定性执行内核”Substrate 的 Runtime 默认以 WebAssemblyWASM字节码形式部署这常被简化为“为了安全沙箱”。但这只是表象。WASM 在 Substrate 中扮演的角色远比一个简单的隔离层深刻得多。它本质上是一个确定性、可验证、可热更新的“执行内核”Execution Kernel。这个内核的特性直接决定了基于 Substrate 构建的 agent 系统能否满足生产级要求。首先“确定性”是区块链的基石也是 agent 可信协作的前提。WASM 的指令集是严格定义的其执行不依赖于宿主 CPU 的浮点运算精度、内存对齐方式、甚至操作系统调度策略。一个 WASM 模块在 Intel Xeon、AMD EPYC、Apple M1 上只要输入相同输出必然相同。这对于 agent 来说至关重要——想象一个由多个 agent 协作完成的复杂任务如Agent A 规划、Agent B 调用 API、Agent C 生成报告如果每个 agent 的本地计算结果因硬件差异而不同整个协作链条就会崩塌。WASM 从执行层面消除了这种不确定性。其次“可验证”是信任的来源。Substrate 的 WASM Runtime 支持Code Hash 验证。每个 pallet 的 WASM 二进制文件在编译后会生成一个唯一的 Blake2b 256-bit hash。这个 hash 被写入链上 storage。当 Runtime 执行时它会先校验当前加载的 WASM 代码是否与链上记录的 hash 匹配。这意味着任何人都可以离线下载 pallet 的源码自己编译计算 hash并与链上 hash 对比从而 100% 确认链上运行的代码与开源代码完全一致。这为 agent 的行为提供了终极审计能力——你可以确信那个负责处理敏感数据的DataGuardianagent其逻辑没有被任何人篡改。最后“可热更新”是运维的生命线。Substrate 支持Runtime 升级Runtime Upgrade。无需停机、无需硬分叉只需一个治理提案通过就可以将新的 WASM 二进制推送到链上替换旧的 Runtime。这个过程是原子的旧 Runtime 在最后一个区块执行完毕新 Runtime 从下一个区块开始生效。对于 agent 系统这意味着 bug 修复、功能增强、安全补丁都可以在分钟级完成。我曾在一个生产环境中为一个处理金融交易的PaymentRouteragent 的 pallet 修复了一个边缘 case 的溢出漏洞从发现到全网生效仅用了 17 分钟——这在传统微服务架构中需要协调多个团队、回滚数据库、灰度发布耗时数小时。然而WASM 并非万能。它的限制也必须被清醒认识无 I/OWASM 模块无法直接访问网络、文件系统或系统时钟。所有外部交互都必须通过 Host Function宿主函数暴露。Substrate 的sp_iocrate 就是这一层的抽象。例如sp_io::offchain::timestamp()提供时间sp_io::http_request::send()提供 HTTP 请求。这意味着 agent 的所有“外部世界”交互都必须经过 Runtime 的严格审查和授权。内存受限WASM 的线性内存是固定大小的默认 128MB。超出则 OOM。这迫使开发者必须精打细算——agent 的状态缓存、临时计算数组都必须在内存预算内设计。无多线程WASM 当前标准不支持多线程。所有计算都是单线程的。这要求 agent 的逻辑必须是高度异步和非阻塞的。幸运的是Substrate 的 Runtime 使用wasmi或parity-wasm解释器它们对async/await有良好支持可以通过sp_runtime::offchain::storage::StorageValue等 API 实现异步等待。提示不要试图在 WASM Runtime 中做重计算。例如一个 agent 需要解析一个大型 JSON。正确的做法是在 offchain worker 中完成解析将结果存入 offchain storage然后在 onchain 调用中读取这个预处理结果。把 heavy lifting 放在 offchain把决策和状态变更放在 onchain这是 Substrate 的黄金法则。4. Offchain Worker 与 Onchain Logicagent 的“大脑”与“脊髓”分工模型Substrate 最常被误解的特性之一就是它的Offchain WorkerOCW。很多人把它当成一个“后台定时任务”或者“链下计算的辅助工具”。这是一种严重的降维理解。OCW 的真实定位是 Substrate 架构中连接链上确定性世界与链下不确定性世界的“神经中枢”。它与 Onchain Runtime 的配合构成了一个完美的 agent 分工模型Onchain 是 agent 的“脊髓”Spine——负责状态存储、权限控制、共识决策、不可篡改的事件广播Offchain Worker 是 agent 的“大脑”Brain——负责感知外部世界、执行复杂计算、发起主动行为、与外部系统交互。这个模型的威力在一个典型的 “AI Model Orchestrator” agent 场景中体现得淋漓尽致。设想一个 agent其职责是监控多个 AI 模型服务的健康状况并在某个模型响应超时时自动将其从负载均衡池中剔除并通知运维人员。Onchain Spine脊髓部分存储所有模型服务的注册信息URL、权重、健康状态。定义set_health_status(model_id, status)调用用于更新状态。发出ModelHealthChanged事件。实现一个is_model_healthy(model_id)的只读函数供其他 pallet 查询。Offchain Worker Brain大脑部分在每个区块产生后自动运行。读取 Onchain 上的模型列表。对每个模型 URL 发起 HTTP GET 请求测量响应时间。如果响应时间 阈值调用sp_io::offchain::storage::set()将“需下线”标记写入 offchain storage。如果 offchain storage 中存在“需下线”标记且该模型当前状态为Healthy则构造一个set_health_status的 unsigned transaction无签名交易提交到交易池。这个流程的关键在于所有“决策”判断是否超时和“感知”发起 HTTP 请求都在 OCW 中完成而所有“状态变更”和“权威记录”都在 Onchain 中完成。OCW 是无状态、无共识、可失败的——即使某个节点的 OCW 因网络问题未能执行其他节点的 OCW 会在下一个区块继续尝试不会影响链的最终一致性。而 Onchain 的set_health_status调用一旦被打包进区块就成为全网公认的、不可篡改的事实。实操中我遇到过一个经典陷阱试图在 OCW 中直接修改 Onchain storage。这是绝对禁止的。OCW 的代码运行在节点本地它没有权限直接写入链上 state。它只能通过两种方式影响链上状态提交 Unsigned Transaction如上例所示。这是一个“提议”需要被其他节点验证并打包。它的优势是去中心化劣势是延迟至少一个区块。使用sp_io::offchain::storage::set()写入 Offchain Storage这是一个本地存储仅对该节点可见。它常被用作 OCW 的“暂存区”或“状态缓存”为下一次 OCW 运行提供上下文。例如记录上次检查的时间戳避免重复检查。另一个重要技巧是OCW 的“重试”与“指数退避”。HTTP 请求失败是常态。OCW 提供了sp_io::offchain::http::Request::retry()方法但更重要的是你需要设计自己的退避策略。我通常的做法是在 offchain storage 中记录一个last_failure_time和retry_count。每次失败retry_count并根据retry_count计算下次重试的间隔如2^retry_count秒。这避免了在网络抖动时所有节点疯狂重试导致目标服务雪崩。注意OCW 的执行是“尽力而为”Best Effort不是“必须成功”Must Succeed。它的设计哲学是“允许失败但保证最终一致性”。因此任何关键业务逻辑都不能只依赖 OCW 的单次执行。必须设计成幂等的、可重入的。例如set_health_status调用本身必须是幂等的——多次设置同一个 model 的Unhealthy状态结果应该完全一样。5. 与 Kubernetes 和 OCI 的共生Substrate Runtime 作为 agent 的“可信执行层”当我们将视野从区块链扩展到更广阔的云原生世界Substrate 的定位就变得异常清晰它不是一个要取代 Kubernetes 的“新编排器”而是为 Kubernetes 上运行的 agent 提供一个可嵌入、可验证、可升级的“可信执行层”Trusted Execution Layer。这种共生关系正是 “substrate”、“kubernetes”、“OCI”、“agent” 这些热词交汇的根本原因。Kubernetes 的核心价值在于其声明式的、面向终态的资源编排能力。它管理 Pod、Service、ConfigMap 等资源确保系统始终处于用户期望的状态。但它不解决一个问题如何确保 Pod 中运行的 agent 代码是用户声称的那个版本且其行为是可预测、可审计的OCI 镜像解决了代码分发问题但镜像本身是一个黑盒——你无法在不运行它的情况下确认其内部逻辑是否符合安全策略。而 Substrate 的 WASM Runtime恰恰填补了这个空白。设想一个典型部署流程开发Agent 开发者使用 Substrate SDK 编写一个DataAnonymizerpallet实现 GDPR 合规的数据脱敏逻辑。构建cargo build --release --featuresruntime-benchmarks生成 WASM blob并计算其 Blake2b hash。验证开发者将源码、WASM blob、hash 公布在 GitHub。第三方审计机构可以独立编译、验证 hash出具审计报告。打包将审计报告、WASM blob、以及一个轻量级的 Rust 二进制作为 WASM host打包成一个 OCI 镜像。这个镜像的Dockerfile可能只有几行FROM rust:1.75-slim COPY target/release/data-anonymizer-runtime.wasm /app/runtime.wasm COPY entrypoint.sh /app/entrypoint.sh CMD [/app/entrypoint.sh]部署Kubernetes 的 Helm Chart 或 Kustomize 配置将此 OCI 镜像部署为一个 StatefulSet。每个 Pod 运行一个 Substrate Runtime 实例加载/app/runtime.wasm。治理当需要升级时新的 WASM blob 被上传到链上治理提案通过后所有 Pod 的 Runtime 自动热更新——无需滚动更新、无需重启 Pod。在这个模型中Kubernetes 负责“在哪里运行”WhereOCI 负责“分发什么”What而 Substrate Runtime 负责“确保它按承诺运行”How it runs。三者各司其职形成一个完整的可信栈。这种架构带来了几个颠覆性的优势零信任部署运维人员无需信任镜像的构建者。他们只需信任 Substrate 的 WASM 验证机制。只要链上 hash 与本地 WASM 匹配代码就是可信的。跨平台一致性无论 agent 运行在 x86 物理机、ARM64 树莓派还是 Apple Silicon Mac 上WASM 的确定性保证了行为完全一致。这极大简化了异构环境下的测试和 QA 流程。细粒度权限控制Substrate 的Origin系统可以精确到“哪个账户、在哪个 pallet、调用了哪个函数”。这比 Kubernetes 的 RBAC 更细粒度。一个 agent 可以被授予只读Balances的权限但无权调用Sudo。内置可观测性所有Event都是结构化的、可索引的。一个 Prometheus exporter 可以轻松监听AgentExecuted事件生成实时的 agent 执行成功率、延迟分布图。我曾在一个医疗影像分析项目中实践过这套方案。客户要求任何对患者影像的处理都必须有完整的、不可篡改的审计日志并且处理算法必须经过 FDA 认证。我们用 Substrate 构建了一个MedicalImageProcessorRuntime将认证过的算法编译为 WASM。每次处理请求都作为一个process_image调用提交到链上。链上自动生成包含patient_id,algorithm_hash,timestamp,result_hash的ImageProcessed事件。Kubernetes 负责扩缩容处理 Pod而 Substrate 保证了每一次处理的合规性与可追溯性。这比单纯在 Kubernetes 上部署一个 Docker 容器多了整整一层法律意义上的可信保障。提示不要试图让 Substrate Runtime 去做 Kubernetes 的事如 Service Discovery、Auto-scaling。它的角色是“执行内核”不是“编排引擎”。最佳实践是Kubernetes 管理 agent 的生命周期启动、停止、重启Substrate Runtime 管理 agent 的逻辑内核加载、执行、升级。两者通过标准化的接口如 gRPC 或 REST通信。6. gVisor 的启示Substrate Runtime 作为用户态“轻量级内核”的演进路径gVisor 的出现是对传统容器安全模型的一次深刻反思。它用一个用 Go 编写的、运行在用户态的“类内核”Sentry拦截并模拟了 Linux 系统调用为容器进程提供了比seccomp和cgroups更强的隔离边界。gVisor 的核心洞见是与其在内核层面打补丁不如在用户态重构一个更小、更可控、更易审计的“执行环境”。这个洞见与 Substrate 的设计理念惊人地一致。Substrate 的 WASM Runtime本质上就是一个为“区块链逻辑”量身定制的、用户态的、轻量级“执行内核”。我们可以将 Substrate Runtime 的架构与 gVisor 进行一个逐层对比层级gVisor (Container)Substrate Runtime (Agent)本质接口层Linux Syscall ABI (open, read, write, socket...)FRAME Pallet Interface (dispatch,on_initialize,on_finalize)定义了“程序”如何与“环境”交互的契约执行层Sentry (Go) 拦截并模拟 syscallWASM Interpreter (wasmi/parity-wasm) 执行 wasm 字节码用户态的、确定性的、可验证的执行引擎状态层VFS (Virtual File System) 模拟文件、网络、设备StorageMap/StorageValue提供键值存储为执行提供持久化、可查询、可授权的状态空间策略层SyscallFilter控制哪些 syscall 可被调用Origin和Weight系统控制谁可以调用、消耗多少资源对执行行为施加的、可编程的、可治理的约束这个对比揭示了一个关键趋势未来的“执行环境”正在从“通用操作系统”向“领域专用内核”演进。gVisor 是为容器进程定制的Substrate 是为区块链逻辑定制的而面向 agent 的下一代执行环境很可能就是 Substrate Runtime 的一个自然延伸。例如一个LLMOrchestratoragent需要调用外部 LLM API、缓存 token、管理会话状态、执行 RAG 检索。如果我们把这个 agent 的全部逻辑都塞进一个传统的 Python Flask 应用里它会面临诸多问题Python GIL 限制并发、内存泄漏难以排查、升级需要重启、行为难以审计。而如果我们将它的核心能力拆分为 Substrate palletLlmGateway: 封装 API 调用管理 rate limit 和 retry。SessionCache: 提供基于 TTL 的会话状态存储。VectorStore: 实现向量检索的索引和查询逻辑。ToolExecutor: 安全地执行用户指定的工具如 Python 代码沙箱。所有这些 pallet 都编译为 WASM由 Substrate Runtime 加载执行。它的优势是确定性相同的 prompt永远得到相同的 token 数和检索结果。可验证LlmGatewaypallet 的源码公开任何人都可验证它是否真的只调用指定的 API而不会偷偷上传数据。可升级当一个新的、更便宜的 LLM API 出现时只需升级LlmGatewaypallet无需改动整个 agent 应用。资源可控Weight系统可以精确计量一次generate_text调用消耗的计算资源CPU cycles, memory bytes防止恶意 prompt 导致 DoS。这已经超越了传统“agent 框架”的范畴而是在构建一个“AI Agent OS”——一个专为 AI 工作负载优化的、轻量级、可验证、可治理的操作系统内核。Substrate 提供的正是这个内核的骨架和工具链。注意这条路的挑战在于生态。目前 Substrate 的 pallet 生态主要围绕金融、治理、身份等 Web3 场景。要让它成为 AI agent 的主流执行层需要社区共建大量高质量的、开箱即用的 AI 相关 pallet如llm_gateway,vector_db,tool_caller。这需要开发者、AI 公司、安全审计机构的共同投入。但方向是明确的当 agent 的复杂度和安全性要求达到临界点时一个像 Substrate 这样成熟的、经过实战检验的、可组合的运行时框架将成为必然选择。7. 实战避坑指南从零搭建一个 Substrate-based Agent Registry 的完整链路理论讲完现在进入最硬核的部分手把手从零开始搭建一个真实的、可运行的 Substrate-based Agent Registry。这个 registry 将允许用户注册、查询、注销 agent并为每个 agent 分配一个唯一的、链上可验证的AgentId。我们将严格遵循前述的所有原则并暴露那些只有亲手踩过才会知道的坑。7.1 环境准备Rust 工具链与 Substrate CLI 的精准版本控制第一步永远是环境。Substrate 对 Rust 版本极其敏感。截至 2024 年中强烈推荐使用 Rust 1.75.0。更高版本如 1.76可能因std库的细微变化导致 WASM 编译失败或运行时 panic。这不是危言耸听我曾在一个周五下午花了 4 小时排查一个panic: unreachable错误最终发现是rustc升级到了 1.76.0。安装命令# 卸载所有现有 rustup curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y source $HOME/.cargo/env # 安装指定版本 rustup install 1.75.0 rustup default 1.75.0 # 添加 wasm target rustup target add wasm32-unknown-unknown --toolchain 1.75.0 # 安装 Substrate CLI (v4.0.0-dev) cargo install --git https://github.com/paritytech/substrate.git --branch polkadot-v1.0.0 --force substrate-cli提示polkadot-v1.0.0是一个稳定的分支标签比master分支更可靠。永远不要用--branch master因为 master 是持续集成的前沿随时可能引入 breaking change。7.2 创建 Runtimeconstruct_runtime!的魔鬼细节使用 CLI 创建模板substrate new-node-template my-agent-registry cd my-agent-registry关键修改在runtime/src/lib.rs。我们需要添加pallet-agent-registry。首先在Cargo.toml的[dependencies]中添加pallet-agent-registry { path ../pallets/agent-registry, default-features false }然后在lib.rs的construct_runtime!宏中加入AgentRegistry: pallet_agent_registry::{Pallet, Call, Storage, EventT, ConfigT},这里有一个极易忽略的坑construct_runtime!宏要求所有 pallet 的Storage名称全局唯一。如果你的pallet-agent-registry中定义了一个叫Agents的StorageMap而另一个 pallet比如pallet-balances也定义了一个叫Accounts的StorageMap这没问题。但如果两个 pallet 都定义了Agents编译会直接失败错误信息晦涩难懂duplicate definition of Agents。解决方案是为每个 pallet 的 storage item 加上唯一的前缀。在pallet-agent-registry/src/lib.rs中#[pallet::storage] #[pallet::getter(fn agents)] pub type AgentsT StorageMap _, Blake2_128Concat, AgentId, AgentInfoT::AccountId, ValueQuery, ;这里的AgentsT是类型名不影响链上存储 key。真正影响的是#[pallet::getter(fn agents)]它生成的 getter 函数名是agents。只要 getter 函数名不冲突即可。但为了清晰我习惯在 getter 名前加 pallet 名fn agent_registry_agents。7.3 编写 PalletAgentId的生成与Origin的正确使用pallet-agent-registry/src/lib.rs是核心。定义AgentId// 使用 Blake2_256 哈希确保全局唯一且不可预测 pub type AgentId [u8; 32]; #[pallet::call] implT: Config PalletT { #[pallet::weight(100_000_000)] pub fn register_agent( origin: OriginForT, name: BoundedVecu8, ConstU3232, endpoint: BoundedVecu8, ConstU32256, ) - DispatchResultWithPostInfo { // 必须是 signed origin即普通账户不能是 root 或 none let who ensure_signed(origin)?; // 生成 AgentId: hash(who name block_number) let block_number frame_system::PalletT::block_number(); let mut payload Vec::new(); payload.extend_from_slice(who.encode()); payload.extend_from_slice(name.encode()); payload.extend_from_slice(block_number.encode()); let agent_id Blake2_256::hash_of(payload); // 检查是否已存在 if AgentsT::contains_key(agent_id) { return Err(Error::T::AgentAlreadyExists.into()); } // 存储 AgentsT::insert( agent_id, AgentInfo { owner: who, name, endpoint, created_at: block_number, status: AgentStatus::Active, }, ); Self::deposit_event(Event::AgentRegistered { agent_id }); Ok(().into()) } }关键点解析ensure_signed(origin)?这是最安全的 origin 检查。它拒绝Rootsudo和Noneunsignedorigin只接受普通账户签名。很多教程用ensure_root(origin)?这在 agent registry 中是危险的——它允许管理员随意注册 agent破坏了去中心化原则。Blake2_256::hash_of使用 Blake2_256而非 SHA256。Substrate 默认使用 Blake2保持一致性。block_number参与哈希这确保了即使同一个账户在同一个区块注册两个同名 agent也会得到不同的AgentId避免了竞态条件。7.4 Offchain Worker实现 agent 的健康检查与自动注销在pallet-agent-registry/src/lib.rs中添加 OCW 逻辑#[pallet::hooks] implT: Config HooksBlockNumberForT for PalletT { fn offchain_worker(block_number: BlockNumberForT) { // 1. 获取所有 active agent for (agent_id, agent_info) in AgentsT::iter().filter(|(_, info)| info.status AgentStatus::Active) { // 2. 发起 HTTP GET 到 endpoint let url String::from_utf8_lossy(agent_info.endpoint); if let Ok(request) sp_io::offchain::http::Request::get(url) { let pending request .add_header(User-Agent, AgentRegistry-OCW) .send() .ok(); if let Some(response) pending.and_then(|r| r.wait()) { if response.code() ! 200 { // 3. 标记为 unhealthy sp_io::offchain::storage::set( sp_io::offchain::storage::StorageKey(bagent_registry::unhealthy), agent_id.encode(), ); } } } } } }然后在on_initializehook 中处理标记#[pallet::hooks] implT: Config HooksBlockNumberForT for PalletT { // ... 上面的 offchain_worker ... fn on_initialize(_n: BlockNumberForT) - Weight { // 检查 offchain storage 中是否有 unhealthy 标记 if let Some(agent_id_bytes) sp_io::offchain::storage::get(sp_io::offchain::storage::StorageKey(bagent_registry::unhealthy)) { if let Ok(agent_id) TryInto::[u8; 32]::try_into(agent_id_bytes.as_ref()) { if let Some(mut agent_info) AgentsT::get(agent_id) { agent_info.status AgentStatus::Unhealthy; AgentsT::insert(agent_id, agent_info); Self::deposit_event(Event::AgentStatusChanged { agent_id, status: AgentStatus::Unhealthy }); } } } 0 } }