拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Substrate Runtime 模块化:面向 Agent 的可信状态机构建范式

Substrate Runtime 模块化:面向 Agent 的可信状态机构建范式 1. Substrate 不是“另一个区块链框架”它本质是一套可组合的运行时构建范式很多人第一次听说 Substrate是在 Polkadot 生态里——“Polkadot 的底层技术栈”或者在某个新公链的白皮书里看到“基于 Substrate 构建”。于是下意识把它归类为“类似 Cosmos SDK 或 Ethereum 的 Layer-1 开发框架”。这个理解方向没错但严重窄化了它的设计哲学和实际能力边界。Substrate 的核心不是“帮你造一条链”而是提供一套高度解耦、可插拔、可复用的“状态机组装工具集”。它不预设你最终要跑 PoW、PoS 还是无共识的私有链也不强制你用 Rust 写逻辑——虽然官方推荐且生态围绕 Rust 展开更不规定你必须接入 Relay Chain——独立链、测试网、企业内网链全都可以零改造运行。我最早接触 Substrate 是在 2021 年帮一家供应链金融平台做 PoC他们需要一个能快速验证“多级账本跨链凭证流转”的原型系统。当时团队里有熟悉 Solidity 的工程师也有写过 C 高性能交易引擎的老兵。如果选 EthereumSolidity 能快速上手但无法定制共识和存储结构如果选 Hyperledger Fabric又太重、链下组件耦合度高、升级困难。Substrate 的 runtime 模块化设计让我们在两周内就搭出了一个带自定义权限模型、轻量级 BFT 共识、以及可插拔凭证验证模块的链——关键在于所有业务逻辑都写在 Rust 的 pallet模块里而 pallet 之间通过 trait 绑定通信不依赖全局状态或中心化调度器。这种“模块即服务”的抽象比传统微服务更彻底每个 pallet 可以有自己的存储前缀、自己的事件类型、自己的 RPC 接口甚至可以独立启用/禁用就像 Linux 内核模块一样热插拔。这背后的技术锚点是 Substrate 的FRAMEFramework for Runtime Aggregation of Modularized Entities。它不是一套 API 库而是一套编译期元编程范式。当你写#[pallet::call]宏时Substrate 的 macro engine 会在编译阶段自动为你生成 dispatch logic、storage layout、event encoding、RPC binding 等全部胶水代码。这意味着你写的每一行业务逻辑天然具备可验证性、可组合性、可升级性。这不是“框架给你封装好了”而是“框架把重复劳动从你代码里彻底删除了”。所以当别人说“Substrate 上手门槛高”真正卡住的往往不是 Rust 语法而是思维切换——从“写一个服务”转向“定义一个状态转换规则集”。提示Substrate 的 runtime 不是虚拟机字节码也不是 WASM 沙箱里的孤立程序。它是原生编译的 Rust 二进制在节点启动时直接加载到内存中执行。这意味着你能用unsafe块做极致优化比如零拷贝序列化也能调用标准库的std::collections::HashMap——但代价是你必须对内存安全负全责。这也是为什么 Substrate 强烈推荐使用frame_support::StorageMap而非原生 HashMap前者在底层做了 storage root 计算、键值编码、版本迁移等全套保障后者只管存取其他全靠你自己兜底。再看热搜词里反复出现的agent和kubernetes表面看和 Substrate 无关实则暗合其架构基因。Kubernetes 的核心思想是“声明式 API 控制器模式”而 Substrate 的 pallet 就是 runtime 层的“控制器”你声明一个pallet_balances::Account存储项框架自动为你生成增删改查的 dispatch 函数、事件触发机制、以及与 extrinsic 生命周期绑定的校验逻辑。Agent尤其 AI Agent强调“目标驱动、自主决策、工具调用”Substrate 的Call枚举和Origin类型正是这种能力的底层映射——每个 extrinsic 都携带明确的调用者身份Origin::Signed(who)、目标 palletBalances::transfer、参数dest, value和权重weight整个链的状态变迁就是无数个 agent用户、合约、治理提案按规则发起的、可审计的行动流。这种“状态机即协议”的设计让 Substrate 天然适配 agent 系统的可信执行环境需求远超传统 Web2 后端的 request-response 模型。2. Runtime 模块化不是“拆代码”而是重构状态变更的契约关系很多团队在 Substrate 项目初期会陷入一个典型误区把 pallet 当成普通 Rust crate 来组织比如建一个pallet-my-nft里面塞满业务逻辑、数据库操作、外部 API 调用。结果很快发现单元测试难写、升级风险高、与其他 pallet 交互耦合严重。问题根源在于没吃透 Substrate 的“状态契约”思想——每个 pallet 不是“功能包”而是“状态变更的最小可信单元”它对外暴露的不是函数而是Call、Event、Storage、Config四个契约接口。我们曾接手一个社区 DAO 项目原团队用单个 pallet 实现了投票、提案、资金池、NFT 发行全部功能。当需要增加“链下预言机喂价”时他们试图在原有 pallet 里加feed_price函数结果导致整个 runtime 编译失败因为新增的 storage item 改变了 pallet 的 storage root而旧区块的 state proof 无法验证新格式。后来我们重构为三个独立 palletpallet-dao-voting只管投票逻辑和状态、pallet-treasury只管资金池和支出审批、pallet-oracle只管价格数据存储和签名验证。它们之间不互相调用函数而是通过Dispatchabletrait 和T::Currency::transfer这样的跨 pallet 调用约定通信。比如 treasury 批准一笔支出后不是自己去扣钱而是发出TreasuryEvent::Awarded事件由pallet-balances的事件监听器捕获并执行转账。这种松耦合让每个 pallet 的升级完全独立只要Event格式不变pallet-oracle升级到 v2.0 时pallet-treasury完全无感。具体来看这四个契约接口如何定义一个 pallet 的“行为边界”2.1 Call不是函数列表而是状态变更的“动作指令集”#[pallet::call]宏定义的不是方法而是extrinsic 可触发的原子操作集合。每个fn对应一个可被用户签名提交的动作比如#[pallet::call] implT: Config PalletT { #[pallet::weight(T::WeightInfo::create_asset())] pub fn create_asset( origin: OriginForT, id: AssetId, name: Vecu8, symbol: Vecu8, ) - DispatchResultWithPostInfo { // 校验 origin 是否有权限 ensure_root(origin)?; // 校验 id 是否未被占用 ensure!(!Assets::T::contains_key(id), Error::T::AssetExists); // 写入 storage Assets::T::insert(id, Asset { name, symbol }); // 发出事件 Self::deposit_event(Event::AssetCreated { id }); Ok(().into()) } }注意三点第一origin参数强制校验调用者权限这是 Substrate 的安全基线第二ensure!宏不是简单 panic而是返回DispatchError会被 runtime 捕获并计入 block weight第三Self::deposit_event不是日志打印而是将事件写入 block 的 event log供前端订阅。Call 的设计哲学是“最小权限原则”每个动作只做一件事且必须显式声明其资源消耗weight和失败路径error。这直接对应 agent 系统中的 “tool call” —— 一个 tool 只解决一个明确问题输入输出严格定义失败时返回结构化 error。2.2 Storage不是数据库表而是状态树的“确定性快照”Substrate 的 storage 不是 key-value store而是Merkle Patricia Trie 的叶子节点映射。#[pallet::storage]宏生成的不是变量而是 trie 中的路径前缀。比如#[pallet::storage] #[pallet::getter(fn assets)] pub type AssetsT StorageMap _, Blake2_128Concat, AssetId, Asset, ValueQuery, ;这里Blake2_128Concat是 key 的哈希算法ValueQuery表示查询不存在时返回 default 值而非 None。关键在于每次写入 storage都会触发整棵 trie 的 root hash 重新计算。这个 root hash 就是 block header 的state_root也是轻客户端验证状态的唯一依据。所以当你在 pallet 里用Assets::T::insert实际发生的是计算Blake2_128Concat(id)得到 trie path将Asset序列化后写入该路径并递归更新所有父节点 hash。这种设计让 storage 天然支持“状态证明”——你可以向第三方提供一个proof一系列 trie 节点证明某条记录在某个 block 的 state_root 下确实存在而无需同步整个链。注意不要在 storage 中存大对象。Trie 的深度和节点大小直接影响验证成本。我们曾有个 pallet 存储用户头像 base64 字符串单个 entry 超过 1MB导致 light client 同步时内存爆掉。正确做法是存 CID如 IPFS hash把大文件放链下链上只存哈希和所有权。2.3 Event不是日志而是状态变更的“公开广播信道”#[pallet::event]定义的不是字符串日志而是runtime 层的标准化消息总线。每个 event 必须实现IntoEventRecord包含phase是否在 block 内部触发、topics用于索引的关键词、data序列化后的 payload。前端通过api.query.system.events()订阅后端通过rpc_methods::state_getStorage查询历史。Event 的设计原则是“只读、不可变、最小化”——它不改变状态只宣告状态已变一旦 emit永远不可修改payload 只含必要字段避免冗余。这完美匹配 agent 系统的 observation 机制agent 不需要轮询状态而是监听特定 event如Transfer { from, to, amount }来触发下一步 action。2.4 Config不是配置文件而是 pallet 间的“编译期契约”#[pallet::config]trait 不是运行时读取的 JSON而是Rust 编译器检查的类型约束。比如pallet-balances要求T: frame_system::Config意味着调用它的 pallet 必须提供frame_system::Config的所有关联类型如BlockNumber,AccountId,Hash。这种设计强制了 pallet 间的兼容性如果你的 pallet 依赖pallet-timestamp就必须在Config中声明type TimestampProvider: UnixTime并在 runtime 实现时传入具体的Timestamp实例。Config 是 Substrate 实现“零运行时反射”的关键——所有依赖关系在编译期解析没有 magic string没有动态加载也就没有 runtime 的不确定性。这比 Kubernetes 的 CRDCustom Resource Definition更彻底CRD 是运行时注册的 schema而 Substrate 的 Config 是编译期硬编码的类型契约。3. FRAME 与 WASM为什么 Substrate 要同时支持原生和 WASM runtimeSubstrate 的 runtime 有两种执行模式原生Native和 WASMWebAssembly。这不是简单的“双引擎备份”而是针对不同信任模型和部署场景的深度架构选择。原生 runtime 是 Rust 编译的二进制直接在 CPU 上执行性能最优WASM runtime 是编译成 WASM 字节码在 WASM 虚拟机如 wasmtime中执行沙箱隔离。两者共存构成了 Substrate 的“可信计算分层”能力。我们做过一个对比实验在相同硬件上运行一个包含 10 个 pallet 的 runtime处理 1000 笔 transfer extrinsic。原生模式平均耗时 8.2ms/blockWASM 模式 12.7ms/block。差距看似不大但关键在确定性Determinism和可验证性Verifiability。WASM 的执行环境是严格定义的指令集、内存模型、浮点运算规则全部标准化。这意味着同一个 WASM blob在任何符合 spec 的 runtime 上执行必然产生完全相同的 state root。而原生二进制依赖于编译器版本、CPU 架构、操作系统 ABI——同一份 Rust 代码在 x86_64 Linux 和 ARM64 macOS 上编译可能因浮点优化差异导致 subtle 的 hash 不一致。这就是为什么 Polkadot 的 parachain 必须提交 WASM runtimevalidator 节点可以是不同厂商的机器但必须对同一 block 达成完全一致的状态共识。但 WASM 不是万能的。它的内存限制默认 4GB、启动开销JIT 编译、以及无法直接调用系统 API如文件读写、网络请求让它不适合某些场景。比如一个需要实时读取传感器数据的工业 IoT 链pallet 必须调用libc::read读取/dev/ttyUSB0这只能在原生模式下实现。Substrate 的解决方案是“WASM 为主原生为辅”所有 consensus-critical 的逻辑如 block production、state transition validation必须能在 WASM 中执行而辅助性、非共识的功能如 telemetry reporting、本地 key management可以放在原生扩展中。具体到开发层面你需要在 runtime 的Cargo.toml中同时定义两个 feature# runtime/Cargo.toml [features] default [std] std [ frame-support/std, frame-system/std, # ... 其他 std 依赖 ] # WASM feature不启用 std no_std [ frame-support/no_std, frame-system/no_std, # ... 其他 no_std 依赖 ]然后在src/lib.rs中用#[cfg(feature std)]分离代码#[cfg(feature std)] pub mod offchain_worker { use super::*; // 这里可以调用 std::fs::read_to_string pub fn fetch_external_data() - ResultVecu8, Boxdyn std::error::Error { std::fs::read(/tmp/sensor_data.json) } } #[cfg(not(feature std))] pub mod offchain_worker { // WASM 模式下此模块为空或提供 mock 实现 }这种设计让同一个 pallet 既能跑在完全隔离的 WASM 环境又能利用原生能力做增强而无需 fork 两套代码。它本质上是一种“渐进式可信”架构核心逻辑在 WASM 中保证绝对确定性外围功能在原生中提升实用性两者通过 well-defined interface如 offchain worker API通信。提示WASM blob 的 size 直接影响 block propagation 时间。我们曾遇到一个 pallet 因引入serde_json导致 WASM size 超过 1MBvalidator 节点同步失败。解决方案是用scale-codec替代 JSON 序列化Substrate 原生序列化格式并用#![no_std]alloccrate 替代std。最终 WASM size 降到 320KBblock time 降低 15%。再看热搜词中的gVisor和OCI它们与 Substrate 的 runtime 隔离理念惊人地一致。gVisor 是 Google 开发的用户态内核拦截 syscalls 并在 sandbox 中模拟为容器提供强隔离OCIOpen Container Initiative定义了容器镜像和 runtime 的标准确保不同厂商的 runtimerunc, kata, gVisor能运行同一镜像。Substrate 的 WASM runtime 就是区块链领域的“OCI spec”——它定义了 runtime 的 ABI、memory layout、trap handling 等标准让不同实现wasmtime, wasmer, parity-wasm都能执行同一份 chain spec。这种标准化正是 agent 系统跨平台部署的基础你的 agent logicpallet写一次就能在任何支持 Substrate WASM 的环境中运行无需关心底层是 bare metal 还是 cloud VM。4. Agent 与 Substrate 的交汇点为什么下一代可信执行环境需要 runtime 级别的 agent 支持当热搜词里agent和substrate频繁共现绝非偶然。AI Agent 的核心挑战是“可信执行”如何确保 agent 的决策过程、工具调用、记忆读写不被恶意篡改、不被中间人劫持、不因单点故障丢失现有方案要么依赖中心化 server如 OpenAI 的 function calling要么在不可信环境浏览器 JS中运行安全性存疑。Substrate 提供的是一个“链上 agent runtime”—— 把 agent 的核心逻辑planning、tool selection、memory update作为 pallet 写入 blockchain由去中心化 validator 网络共同执行和验证。我们正在落地的一个案例是“合规审计 agent”。传统审计需要人工翻查数万行代码和日志效率低、易出错。我们的方案是将审计规则如“所有转账必须经过 KYC 验证”、“智能合约不得调用外部 API”编码为pallet-audit-rule的Call将审计报告生成逻辑写入pallet-audit-reportagent 的“思考”过程就是提交一系列 extrinsic 到链上触发这些 pallet 的状态变更。比如用户提交AuditRequest { target_contract: 0xabc... }pallet-audit-engine触发scan_code调用pallet-static-analysis的analyze_bytecode分析结果存入pallet-audit-result的 storage最终pallet-audit-report汇总所有结果生成Report { score: 92, issues: [...] }整个流程的每一步都在链上留下不可篡改的 trace谁发起、何时发起、用了哪个 pallet、输入什么参数、输出什么结果、消耗多少 weight。这比任何中心化 SaaS 审计平台都更透明、更可验证。更重要的是agent 的“记忆”可以天然映射到 Substrate 的 storage短期记忆working memory用StorageValue存临时状态长期记忆knowledge base用StorageMap存结构化数据永久记忆audit log用 event log 永久存档。不需要额外搭建 Redis 或 PostgreSQL链本身就是一个分布式、高可用、强一致的记忆系统。这种架构解决了 agent 开发的三大痛点4.1 工具调用的安全边界问题当前 agent 框架如 LangChain的 tool call 是在 Python 进程内执行调用requests.get或subprocess.run完全暴露在 host OS 中。恶意 tool 可能窃取密钥、删库跑路。Substrate 的 solution 是“tool pallet 化”每个 tool 必须实现为独立 pallet其Call函数受 runtime 权限控制。比如pallet-http-client的get函数只能访问白名单域名配置在Config中且返回数据必须经scale-codec序列化不能直接返回 raw bytes。validator 在执行时会检查该 pallet 是否被启用、调用者是否有权限、URL 是否在白名单——所有校验都在 WASM sandbox 内完成host OS 完全无感知。4.2 记忆系统的持久化与一致性难题Agent 的记忆常存于向量数据库如 ChromaDB面临数据漂移、索引失效、并发冲突等问题。Substrate 的 storage 是 ACID 的StorageMap::try_mutate提供原子性读写StorageValue::mutate保证单 key 更新的线程安全。我们用pallet-vector-store实现了一个基于 HNSW 算法的链上向量索引所有插入、查询、删除操作都封装在 pallet 的Call中。由于 storage root 的 Merkle 证明你可以向第三方证明“在 block #1234567key X 的 embedding 向量确实是 [0.1, 0.9, ...]”而无需信任任何中心化服务。4.3 执行环境的可验证性缺失LLM-based agent 的推理过程是黑盒用户无法验证其是否真的按 prompt 执行。Substrate 的 solution 是“推理逻辑 pallet 化 ZK-SNARK 验证”。我们将 LLM 的 prompt engineering、tokenization、logit sampling 等步骤用 Rust 实现为pallet-llm-inference。虽然无法在链上跑完整 LLM但关键决策点如“选择 tool A 而非 B 的依据”可以生成 ZK proof证明该决策符合预设规则。proof 提交到链上由轻客户端验证而不需重放整个推理过程。这实现了“可验证的智能”而非“可信任的智能”。注意这不是要取代 LLM而是为其提供可信执行层。真正的 LLM inference 仍在链下高性能 GPU 上运行链上 pallet 只负责接收 input、验证 LLM 的 output signature、执行 state transition、存证 decision trace。这种 hybrid 架构兼顾了性能与可信。最后看kubernetes device plugin这个热词。K8s 的 device plugin 允许 pod 使用 GPU、FPGA 等硬件资源而 Substrate 的pallet-hardware-attestation正在做类似的事它通过 Intel SGX 或 AMD SEV 技术证明某个 validator 节点的 WASM runtime 确实在可信执行环境TEE中运行。这意味着你的 agent 逻辑不仅在链上执行还在硬件级隔离的 enclave 中执行——连 validator 自己都无法窥探内存中的敏感数据如 private key、LLM weights。这才是真正的“零知识 agent”你知道它在工作但不知道它怎么工作。5. 从零开始一个可运行的 Substrate Agent Runtime 实战指南纸上谈兵不如动手一试。下面我带你用 Substrate CLI 创建一个极简但完整的 “Hello Agent” runtime它包含一个 agent 注册 pallet、一个 tool 调用 pallet、一个链上 memory pallet。整个过程不超过 20 分钟所有代码均可在本地node-template上运行无需连接公网。5.1 环境准备避开最常踩的三个坑首先确认你的 Rust 环境# 必须用 nightly因为 Substrate 依赖 unstable features rustup default nightly rustup update nightly # 安装 wasm 构建工具 rustup target add wasm32-unknown-unknown --toolchain nightly # 安装 Substrate CLI最新稳定版 cargo install substrate-node-template --version 4.0.0-dev坑一Node.js 版本陷阱。Substrate 的 frontend template如 Polkadot-JS Apps要求 Node.js 18。如果你用 nvm执行nvm install 18 nvm use 18。否则yarn install会报错ERR_OSSL_EVP_UNSUPPORTED。坑二Windows 用户的 WSL 问题。不要在 Windows CMD 或 PowerShell 中运行substrate。必须用 WSL2Ubuntu 22.04且确保wsl --update到最新版。否则cargo build --release会卡在wasmparser编译。坑三磁盘空间不足。cargo build --release编译原生 runtime 会占用 8GB 内存和 20GB 磁盘。建议在 SSD 上操作且预留足够 swap spacesudo fallocate -l 4G /swapfile sudo mkswap /swapfile sudo swapon /swapfile。5.2 创建 runtime palletagent_registry进入node-template/runtime/src/新建pallets/agent_registry/src/lib.rs// pallets/agent_registry/src/lib.rs #![cfg_attr(not(feature std), no_std)] use frame_support::{decl_storage, decl_module, dispatch, traits::Get}; use frame_system::ensure_signed; pub trait Config: frame_system::Config { type Event: FromEventSelf IntoSelf as frame_system::Config::Event; } decl_storage! { trait Store for ModuleT: Config as AgentRegistry { // 存储 agent 的 metadataname, owner, tools Agents get(fn agents): map hasher(blake2_128_concat) T::AccountId (Vecu8, VecToolId); // 工具白名单防止 agent 调用危险 tool ToolWhitelist get(fn tool_whitelist): map hasher(blake2_128_concat) ToolId bool; } } decl_module! { pub struct ModuleT: Config for enum Call where origin: T::Origin { // 必须声明事件 fn deposit_event() default; #[weight 10_000] pub fn register_agent( origin, name: Vecu8, tools: VecToolId, ) - dispatch::DispatchResult { let who ensure_signed(origin)?; // 校验 name 长度 ensure!(name.len() 32, Name too long); // 校验 tools 是否都在 whitelist 中 for tool in tools { ensure!(Self::tool_whitelist(tool), Tool not whitelisted); } // 写入 storage AgentsT::insert(who, (name, tools)); Self::deposit_event(RawEvent::AgentRegistered(who)); Ok(()) } } } // 定义事件 decl_event!( pub enum EventT where AccountId T as frame_system::Config::AccountId, { AgentRegistered(AccountId), } ); // 定义 tool id 类型 #[derive(Clone, Encode, Decode, PartialEq, Eq, Debug, Copy, Default)] pub struct ToolId(u32); impl Fromu32 for ToolId { fn from(id: u32) - Self { ToolId(id) } }在runtime/src/lib.rs中注册 pallet// runtime/src/lib.rs // 在 construct_runtime! 宏中添加 construct_runtime!( pub enum Runtime where Block Block, NodeBlock opaque::Block, UncheckedExtrinsic UncheckedExtrinsic { // ... 其他 pallet AgentRegistry: pallet_agent_registry::{Module, Call, Storage, EventT}, // ... } );5.3 实现 tool 调用 pallettool_executor创建pallets/tool_executor/src/lib.rs// pallets/tool_executor/src/lib.rs #![cfg_attr(not(feature std), no_std)] use frame_support::{decl_storage, decl_module, dispatch, traits::Get}; use frame_system::ensure_signed; pub trait Config: frame_system::Config { type Event: FromEventSelf IntoSelf as frame_system::Config::Event; } decl_storage! { trait Store for ModuleT: Config as ToolExecutor { // 模拟一个安全的 tool获取当前 block number BlockNumberTool get(fn block_number_tool): Optionu32; } } decl_module! { pub struct ModuleT: Config for enum Call where origin: T::Origin { fn deposit_event() default; #[weight 5_000] pub fn execute_tool( origin, tool_id: ToolId, ) - dispatch::DispatchResult { let _who ensure_signed(origin)?; // 只允许调用 block_number_tool ensure!(tool_id ToolId(1), Invalid tool id); // 获取当前 block number let block_num frame_system::ModuleT::block_number(); // 存入 storage模拟 tool 输出 BlockNumberToolT::put(block_num.try_into().unwrap_or(0)); Self::deposit_event(RawEvent::ToolExecuted(tool_id, block_num)); Ok(()) } } } decl_event!( pub enum EventT where AccountId T as frame_system::Config::AccountId, { ToolExecuted(ToolId, u32), } );5.4 添加链上 memorymemory_store创建pallets/memory_store/src/lib.rs// pallets/memory_store/src/lib.rs #![cfg_attr(not(feature std), no_std)] use frame_support::{decl_storage, decl_module, dispatch, traits::Get}; use frame_system::ensure_signed; pub trait Config: frame_system::Config { type Event: FromEventSelf IntoSelf as frame_system::Config::Event; } decl_storage! { trait Store for ModuleT: Config as MemoryStore { // 存储 agent 的短期记忆key-value ShortTermMemory get(fn short_term_memory): map hasher(blake2_128_concat) (T::AccountId, Vecu8) Vecu8; } } decl_module! { pub struct ModuleT: Config for enum Call where origin: T::Origin { fn deposit_event() default; #[weight 2_000] pub fn write_memory( origin, key: Vecu8, value: Vecu8, ) - dispatch::DispatchResult { let who ensure_signed(origin)?; // key 长度限制 ensure!(key.len() 64, Key too long); ensure!(value.len() 1024, Value too large); ShortTermMemoryT::insert((who, key), value); Self::deposit_event(RawEvent::MemoryWritten(who, key)); Ok(()) } #[weight 1_000] pub fn read_memory( origin, key: Vecu8, ) - dispatch::DispatchResultWithPostInfo { let who ensure_signed(origin)?; let value Self::short_term_memory((who, key.clone())); // 返回值不计入 weight因为只读 Ok(Some(value).into()) } } } decl_event!( pub enum EventT where AccountId T as frame_system::Config::AccountId, { MemoryWritten(AccountId, Vecu8), } );5.5 编译并启动节点在node-template/目录下# 编译 WASM runtime cargo build --release --featuresruntime-benchmarks # 启动节点清除旧数据 ./target/release/node-template \ --dev \ --tmp \ --ws-port 9944 \ --rpc-cors all \ --rpc-methods Unsafe5.6 用 Polkadot-JS Apps 测试 agent flow打开https://polkadot.js.org/apps/连接到ws://127.0.0.1:9944。注册 agent在Extrinsics标签页选择agentRegistry-register_agent填入name: hello-agenttools: [1]对应 block_number_tool提交。执行 tool选择toolExecutor-execute_tool填入tool_id: 1提交。你会看到ToolExecuted事件。写入 memory选择memoryStore-write_memorykey: last_blockvalue: 123实际值会是当前 block num提交。读取 memory选择memoryStore-read_memorykey: last_block点击Submit Transaction在右下角Developer-Chain State中查询memoryStore.shortTermMemory输入(Alice, last_block)即可看到值。整个流程就是一个最简 agent 的生命周期注册 → 调用 tool → 存储结果 → 读取记忆。所有操作都在链上所有状态都可验证所有事件都可追溯。最后分享一个小技巧在开发中用--execution Native启动节点./target/release/node-template --dev --execution Native可以跳过 WASM 编译极大加速迭代。但上线前务必切回--execution WASM并测试。另外pallet-contract的 ink! 语言更适合写复杂逻辑但 runtime pallet 是性能和确定性的终极选择——就像 Kubernetes 的 operator 和 CRDink! 是应用层pallet 是基础设施层。
返回列表