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

资讯详情

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

Substrate 作为可信执行基座:构建可验证 Agent 状态机

Substrate 作为可信执行基座:构建可验证 Agent 状态机 1. 项目概述Substrate 不是“另一个区块链框架”而是可验证计算的底层操作系统你搜“substrate”时首页弹出的多半是“Substrate 区块链开发框架”“Polkadot 底层技术”这类描述。但如果你真在生产环境里用过 Substrate就会发现它根本不是为“发一条链”而生的——它是为构建可验证、可组合、可升级的可信执行环境TEE替代方案而设计的系统级基础设施。我带团队做过三个落地项目一个是金融级链上合规审计引擎一个是工业设备固件远程验证服务还有一个是跨云环境的零信任策略分发中枢。这三个项目都没上主网也没发代币但都重度依赖 Substrate 的核心能力运行时可升级性、状态机确定性、WASM 执行沙箱、以及原生支持的轻客户端同步协议。它们共同指向一个被长期低估的事实Substrate 的本质是一个面向分布式系统的、带状态证明能力的通用执行内核。它和 Kubernetes 的定位有本质区别——K8s 管理的是容器生命周期与资源调度而 Substrate 管理的是状态演化过程的可验证性与一致性保障。当你看到热词里反复出现 “agent”“OCI”“gVisor”其实正说明行业正在从“容器化部署”走向“可验证行为编排”Agent 不再只是跑在 Pod 里的进程而是需要被证明其决策逻辑未被篡改、其执行路径可回溯、其状态变更符合预定义规则的可信实体。Substrate 提供的正是这个“证明基础设施”。它不关心你跑的是 Rust 还是 WASM 编译的 Python也不限定你用的是 OCI 镜像还是自定义二进制包但它强制要求所有状态变更必须通过可验证的执行函数Call、所有数据存储必须经由可证明的 Merkle-Patricia Trie、所有共识参与必须基于可裁剪的 Grandpa/Babe 模块。这不是“区块链思维”这是“形式化验证思维”在工程侧的落地。所以如果你正面临这样的问题需要让多个异构系统比如边缘设备 云端 AI Agent 第三方 SaaS就某个业务状态达成不可抵赖的一致或者你的 Agent 决策链路太长中间任何一环被篡改都会导致结果失效又或者你正在设计一个需要长期存证、且接受第三方审计的自动化流程——那 Substrate 就不是“备选方案”而是目前工程实践中最接近“开箱即用”的可信执行基座。2. 核心架构拆解为什么 Substrate 的模块化不是“插件式”而是“契约式”很多人第一次看 Substrate 文档会被 pallet模块的概念吸引以为只要把pallet-balances、pallet-timestamp插进去就能跑起来。这就像以为把 Docker CLI 和 containerd 装上就能搞定 K8s 一样混淆了“组件存在”和“契约履行”的本质区别。Substrate 的模块化核心在于每个 pallet 都是一份运行时契约Runtime Contract它必须严格满足三类接口约束状态存储契约Storage API、调用执行契约Call API、事件通知契约Event API。这三者缺一不可且彼此强耦合。举个实际例子我们做工业固件验证时需要一个pallet-firmware-attestation模块。它不能只定义“存哈希值”这个动作还必须明确回答存储契约哈希值存哪里是单值StorageValueHash还是映射StorageMapDeviceId, Hash键的序列化方式是否兼容轻客户端查询执行契约谁可以调用attest_firmware()是仅限 root还是需满足device_is_registered signature_is_valid的复合条件这个条件检查逻辑写在哪是在 pallet 内部硬编码还是通过frame-support::traits::EnsureOrigin抽象为可替换的 Origin 检查器事件契约成功 attestation 后必须 emitFirmwareAttested(DeviceId, Hash, BlockNumber)事件且该事件结构必须能被外部 indexer比如 Subsquid无歧义解析。提示Substrate 的decl_storage!宏早已废弃现在强制使用#[pallet::storage]属性宏。这不是语法糖升级而是将存储定义从“声明式”推向“契约式”——编译器会校验你定义的StorageValue是否实现了Codectrait是否满足MaxEncodedLen约束甚至会检查你是否为可枚举存储StorageMap提供了IterableStorage实现。这些都不是“最佳实践建议”而是编译期强制契约。再来看热词中高频出现的gVisor。gVisor 是 Google 开发的用户态内核用于隔离容器进程。它的核心价值在于提供 syscall 级别拦截与重实现。Substrate 的 WASM 执行环境sc-executor干的是类似的事但粒度更细它拦截的不是 syscall而是 WASM 的call、memory.grow、table.set等指令并将其映射到宿主Host提供的确定性函数上。比如ext_storage_get_version_1这个 Host 函数它不返回当前时间戳而是返回一个由区块头决定的、全网一致的BlockNumber。这种设计让 WASM 代码天然具备“无副作用”特性——你无法在 pallet 中调用std::time::SystemTime::now()因为 WASM 环境根本没暴露这个 syscall。这就是 Substrate 对“可验证性”的底层保障所有非确定性输入必须经由 Host 显式注入且注入点必须可审计、可证明。所以当热词里出现 “agent execution terminated due to error”如果这个 agent 是跑在 Substrate 上的 WASM 模块错误原因大概率不是内存溢出而是 Host 函数调用失败比如ext_crypto_sr25519_verify_version_1返回 false或是存储读取超出了Weight预估上限。这和 Kubernetes 里 Pod OOMKilled 的排查思路完全不同——你需要看的是 runtime 的 weight trace而不是 cgroup memory.stat。3. 实操关键环节从零构建一个可验证的 Agent 状态机含完整配置与参数推导我们以一个真实场景切入为某智能合约审计平台开发一个链上 Agent职责是自动扫描新部署的 ERC-20 合约检查是否存在重入漏洞Reentrancy并将结果以可验证方式存证。这个 Agent 不能只是“跑个脚本”它的每一步决策都必须能被第三方独立复现和验证。以下是实操中必须亲手敲定的五个核心环节每个环节都附带参数选择依据和避坑经验。3.1 运行时设计为什么必须放弃“单 pallet 单功能”思维很多新手会建一个pallet-audit-agent把扫描逻辑、漏洞判定、结果存储全塞进去。这是大忌。Substrate 的最佳实践是按“验证责任”而非“功能边界”划分 pallet。我们最终拆成了三个 palletpallet-contract-scanner只负责接收合约字节码Vecu8调用 WASM 内置的evm::analyze()函数输出结构化中间表示IR。它不存结果也不做判定。pallet-vuln-detector接收 IR运行确定性规则引擎基于regexcrate 的预编译 DFA输出VulnReport { contract_id: H160, vuln_type: Reentrancy, locations: Vecu32 }。它不接触链上存储只做纯计算。pallet-audit-ledger定义AuditResultAccountId, H160, VulnReport, BlockNumber存储项提供submit_audit_result()调用但强制要求调用者提供 Merkle Proof证明该结果是由前两个 pallet 在指定区块高度生成的。注意pallet-audit-ledger的submit_audit_result()函数签名必须是fn submit_audit_result(origin, report: VulnReport, proof: Vecu8, block_hash: H256)其中proof是对report的 Merkle Proofblock_hash是生成report时的区块哈希。这个设计让 ledger pallet 完全不信任 scanner 和 detector只信任密码学证明。参数推导VulnReport结构体大小必须严格控制。我们实测发现当locations: Vecu32超过 128 个元素时整个 report 的编码长度会突破 32KB导致交易被 runtime 拒绝默认BlockLength::max为 5MB但单个 storage item 有隐式限制。解决方案不是调大限制而是重构将locations改为MerkleRoot把具体位置存到 off-chain indexer链上只存根哈希。这增加了 1 次 HTTP 查询但换来了链上存储的确定性和可扩展性。3.2 WASM 执行环境配置sc-executor的四个致命参数Substrate 的 WASM executor 不是黑盒它的四个核心参数直接决定 Agent 的行为边界max_memory_size默认 1GB这不是给 WASM 分配的内存上限而是 WASM 线性内存Linear Memory的最大页数。WASM 规范规定每页 64KB所以 1GB 16384 页。但注意sc-executor会预留 256 页给 Host 函数调用栈。实测发现当 Agent 做深度符号执行Symbolic Execution时栈帧极易耗尽这 256 页导致stack overflow。我们的解法是在Cargo.toml中为runtimecrate 添加rustflags [-C, link-arg--stack-first]强制将栈放在内存起始位置腾出更多空间给堆。max_table_elements默认 10000WASM 的 table 是函数指针表。Agent 若使用wasmi或parity-wasm解析复杂 EVM 字节码会动态注册大量函数。超过此限会触发table growtrap。我们将其设为 50000并在 pallet 初始化时预分配table.init。instruction_weight_per_second默认 1000000000这是权重计算的核心。1 个 weight 1 纳秒的 CPU 时间。但注意这个值是理论峰值实际执行受 Host 函数开销影响极大。比如ext_crypto_ed25519_verify一次调用约消耗 50000 weight而ext_storage_set写入 1KB 数据约消耗 10000 weight。我们用frame-benchmarking工具跑满 1000 次audit_contract得到平均 weight 为 23500000于是将交易weight设为Weight::from_parts(23500000, 0)并预留 20% buffer。max_code_size默认 2MBAgent 的 WASM 二进制不能超过此限。我们用wabt工具链压缩wasm-strip audit.wasm wasm-opt -Oz audit.wasm -o audit.opt.wasm体积从 1.8MB 降至 420KB腾出空间给未来规则更新。3.3 轻客户端同步让外部 Agent 验证链上状态的“最小可信集”热词里常提 “kubernetes version: v1.26.0”K8s 版本管理靠的是 manifest 文件哈希。Substrate 的等价物是轻客户端同步协议Light Client Sync Protocol。外部 Agent比如一个 Python 写的审计报告生成器要验证pallet-audit-ledger中的某条记录它不需要同步全链只需获取最新区块头Header的state_root下载该区块头对应的Proof包含目标 storage key 的 Merkle 路径用sp-state-machine::prove_read验证Proof与state_root的匹配性这个过程的关键是Proof的生成。Substrate 默认不开启 full proof 生成需在service/src/lib.rs中修改let client service.new_full_client::RuntimeApi, Executor, _(config)?; // 添加这一行启用 full state proof client.execution_extensions().set_extensions_factory( Box::new(sp_state_machine::create_proof_provider_extension) );但代价是每个区块生成 proof 会增加约 15% 的 CPU 开销。我们的折中方案是只对AuditResult相关的 storage key如AuditLedger::Results开启 selective proof用frame-support::storage::with_transaction包裹关键写操作在 commit 前显式调用generate_proof_for_keys([key])。3.4 权重与费用模型如何让 Agent 调用“不烧钱”但“不被滥用”Substrate 的交易费用 base_fee len_fee weight_fee。其中weight_fee占比超 90%。若不对 Agent 调用加权恶意用户可构造超大字节码触发深度分析耗尽区块 weight。我们的方案是两级权重静态权重对submit_audit_result()调用固定收取Weight::from_parts(10000, 0)覆盖基础验证开销。动态权重对scan_contract()调用按输入字节码长度线性计费weight base (len / 1024) * 5000。这里5000是每 KB 字节码的基准 weight经实测1KB EVM 字节码平均消耗 4800~5200 weight。费用计算代码#[pallet::weight({ let len bytes.len() as u64; Weight::from_parts(10_000 (len / 1024).saturating_mul(5_000), 0) })] pub fn scan_contract(origin, bytes: Vecu8) - DispatchResult { ... }实操心得不要用bytes.len()直接乘系数WASM 的memory.size指令返回的是页数不是字节数。必须用sp_runtime::traits::SaturatedConversion::saturating_from(bytes.len())转换否则超长字节码会导致 weight 溢出交易被静默拒绝。3.5 外部 Agent 集成用 OCI 镜像封装 Substrate Runtime 的正确姿势热词里 “OCI” 和 “Substrate” 同时出现暗示一种新范式把 Substrate runtime 当作一个可版本化、可分发的“可信执行单元”。我们用docker buildx build --platform linux/amd64,linux/arm64 -t audit-runtime:v1.2 .构建多平台镜像Dockerfile 核心是FROM rust:1.75-slim AS builder WORKDIR /runtime COPY . . RUN cargo build --release --featuresruntime-benchmarks FROM gcr.io/distroless/cc-debian11 COPY --frombuilder /runtime/target/release/wbuild/audit-runtime/audit_runtime.compact.compressed.wasm /app/runtime.wasm COPY --frombuilder /runtime/target/release/audit-node /app/node EXPOSE 30333 9933 9944 CMD [/app/node, --dev, --ws-external, --rpc-external]关键点不打包源码只打包.wasm和node二进制。.wasm是 runtime 的 ABI.node是执行宿主二者版本必须严格匹配。镜像标签v1.2必须与 runtime 的VERSION常量一致。我们在runtime/src/lib.rs中定义const VERSION: RuntimeVersion RuntimeVersion {... version: 1_002, ..}CI 流水线强制校验git tag与VERSION一致。启动时禁用--unsafe-rpc-external强制走--ws-external。因为 RPC 调用可能触发非确定性行为如system_health而 WebSocket 只暴露state_getStorage等确定性接口这才是 Agent 应该使用的通道。这样外部 Python Agent 只需import requests # 从 OCI registry 拉取 runtime 镜像启动节点 # 然后用 substrate-interface 库连接 from substrateinterface import SubstrateInterface substrate SubstrateInterface(urlws://localhost:9944) # 调用 pallet-contract-scanner::scan_contract result substrate.rpc_request(contract_scan, [contract_bytes_hex])整个链路完全可重现OCI 镜像哈希、runtime 版本、WASM 字节码哈希、RPC 请求参数四者构成一个可验证的证据链。4. Agent 与 Substrate 的协同模式从“运行在链上”到“与链协同验证”热词中 “agent 开发”“ai agent”“hermes agent” 高频出现但多数人把 Agent 理解为“跑在服务器上的智能程序”。在 Substrate 场景下Agent 的角色必须升维它不再是链的“使用者”而是链的“协作者”与“验证者”。我们总结出三种成熟协同模式每种都对应不同的安全假设和工程复杂度。4.1 模式一Off-Chain WorkerOCW——链下计算链上存证这是最常用也最容易误用的模式。OCW 允许 pallet 在区块生成前从链下获取数据如 API 响应、文件哈希并将其作为交易参数提交。典型误区是把 OCW 当作“定时任务”让它每 5 分钟拉一次股价。这违反了 Substrate 的确定性原则——不同节点拉取的股价可能因网络延迟、API 限流而不同导致区块分叉。正确用法OCW 只做可验证的数据聚合。例如我们为审计平台设计的pallet-price-oracleOCW 并不直接调用 CoinGecko API而是调用一个去中心化预言机合约如 Chainlink获取ETH/USD的answer和updatedAt。它将answer和updatedAt一起提交但不存 answer只存 merkle root of (answer, updatedAt, block_number)。链上 pallet 通过ext_offchain_index::set()将原始数据存入 off-chain index供后续验证。验证时外部 Agent 可以从链上读取merkle_root从 off-chain index 读取(answer, updatedAt, block_number)本地计算 merkle root比对是否一致这样OCW 不承担“数据真实性”责任只承担“数据完整性”责任。数据来源的真实性由预言机合约的经济激励机制保障。4.2 模式二Signed ExtensionsSE——为 Agent 行为添加链上签名凭证热词里 “pi agent”“cursor agent” 暗示 Agent 需要身份标识。Substrate 的SignedExtension是比 ERC-20 approve 更底层的身份授权机制。它允许你在交易提交前插入自定义验证逻辑。我们为审计 Agent 设计了AuditAgentSignatureSEimplT: Config SignedExtension for AuditAgentSignatureT { const IDENTIFIER: static str AuditAgentSignature; type AccountId T::AccountId; type Call T::Call; type AdditionalSigned (H256, u32); // (audit_report_hash, block_height) type Pre (); fn additional_signed(self, call: Self::Call) - ResultSelf::AdditionalSigned, TransactionValidityError { if let Call::ContractScanner(scan_call) call { Ok((scan_call.report_hash, scan_call.block_height)) } else { Err(InvalidTransaction::BadSigner.into()) } } fn validate( self, who: Self::AccountId, call: Self::Call, info: DispatchInfoOfSelf::Call, len: usize, ) - TransactionValidity { let (hash, height) self.additional_signed(call)?; // 验证 who 是否在白名单且 hash 是否已在链上提交 ensure!(T::AuditWhitelist::contains(who), InvalidTransaction::BadSigner); ensure!(AuditLedgerT::contains_key(hash), InvalidTransaction::Stale); Ok(ValidTransaction::default()) } }效果是任何调用ContractScanner::scan_contract的交易都必须携带report_hash和block_height且发送者必须是白名单中的 Agent 账户。这比单纯检查origin Origin::signed(who)更严格——它强制 Agent 的行为与链上已存证的结果绑定。外部监控 Agent 只需监听AuditAgentSignature::validate事件就能实时追踪所有合规 Agent 的活动。4.3 模式三Custom RPC Endpoints——让 Agent 直接调用链上逻辑Kubernetes 有kubectl execSubstrate 的等价物是自定义 RPC 方法。热词中 “agent 部署 测试软件” 暗示需要快速验证 Agent 行为。我们为pallet-vuln-detector添加了 RPC#[rpc_interface] pub trait VulnDetectorApiBlockHash { #[method(name vuln_detect_evm)] fn detect_evm(self, bytecode: Bytes, block_hash: BlockHash) - ResultVecVulnLocation, Error; } // 实现 implC, P VulnDetectorApiC as sp_api::ProvideRuntimeApi::Api for RpcImplC, P { fn detect_evm(self, bytecode: Bytes, block_hash: C::Hash) - ResultVecVulnLocation, Error { // 从 block_hash 获取 runtime api let api self.client.runtime_api(); // 调用 pallet 内部的纯函数不修改状态 let result api.detect_evm_at(block_hash, bytecode.0)?; Ok(result) } }关键优势零 Gas 消耗RPC 调用不产生交易不占用区块 weight适合调试和批量扫描。确定性保证detect_evm_at是一个纯函数输入相同则输出必相同外部 Agent 可以缓存结果。版本隔离block_hash参数确保 Agent 总是调用指定历史区块的 runtime 版本避免因 runtime 升级导致行为突变。我们用这个 RPC 搭建了 CI/CD 流水线每次提交新规则流水线自动下载 1000 个主流 ERC-20 字节码调用vuln_detect_evm比对结果与基线。只有 100% 通过才允许合并。这比在测试网部署快 10 倍且结果 100% 可复现。4.4 模式四State Proofs as First-Class Citizens——把证明本身当作一等公民这是最前沿也最易被忽视的模式。热词中 “a-memguard”“agent memory” 暗示 Agent 需要安全记忆。Substrate 的sp-state-machine::prove_read不是工具函数而是可编程的证明生成器。我们让pallet-audit-ledger提供一个get_proof_for_report(report_hash: H256) - Vecu8RPC它返回一个完整的 Merkle Proof。外部 Agent比如一个浏览器插件拿到这个 Proof 后可以用极小的 JS 库5KB在本地验证// 用 polkadot/util-crypto 的 merkleProofVerify const isValid merkleProofVerify( proof, // 从链上获取 stateRoot, // 当前区块 state_root reportHash, // 要验证的 key reportValue // 预期的 value可选 );这意味着Agent 的“记忆”不再依赖中心化数据库而是直接锚定在链上状态。a-memguard提出的“proactive defense”在 Substrate 语境下就是让 Agent 的每一次记忆读取都伴随一次密码学验证。我们实测一个 4KB 的 Proof 在 Chrome 中验证耗时 3ms完全可以嵌入实时 UI。这彻底改变了 “agent 记忆体系中短期、长期、永久记忆如何实现” 的设计范式——短期记忆session存 localStorage长期记忆knowledge存 IPFS而永久记忆truth的锚点永远是链上 state_root。5. 常见问题与实战排障那些文档里不会写的“血泪教训”Substrate 的文档以严谨著称但有些坑只有亲手填过才知道。以下是我们在三个项目中踩过的、最具代表性的六个问题每个都附带可复制的诊断命令和修复方案。5.1 问题一“Runtime error: Trap occurred in the wasm runtime: wasm trap: unreachable” —— 不是代码 bug是 weight 超限现象Agent 调用scan_contract时节点日志出现unreachable交易状态为Exhausted但 pallet 代码里没有unreachable!()。根因Substrate 的 WASM executor 在检测到交易 weight 超过区块剩余 weight 时会主动触发unreachabletrap而非优雅返回错误。这是为了防止恶意交易耗尽区块资源。诊断# 查看区块 weight 使用情况 curl -H Content-Type: application/json -d {jsonrpc:2.0,method:system_health,params:[],id:1} http://localhost:9933 # 关键字段peers: 0, isSyncing: false, shouldHavePeers: true # 如果 isSyncing: true说明节点卡在同步weight 计算异常 # 查看最近区块的 weight 分布 curl -H Content-Type: application/json -d {jsonrpc:2.0,method:chain_getBlock,params:[0x...],id:1} http://localhost:9933 | jq .result.block.header.digest.logs[] | select(.typePreRuntime)修复在 pallet 的#[pallet::weight]属性中将Weight::from_parts(23500000, 0)改为Weight::from_parts(23500000 * 120 / 100, 0)加 20% buffer在节点启动时增加--execution native参数强制用 native 代码执行比 WASM 快 3~5 倍缓解 weight 压力最终方案将scan_contract拆分为两步——第一步submit_bytecode(bytes)轻量固定 weight第二步trigger_analysis(bytecode_hash)异步由 OCW 触发5.2 问题二“Failed to fetch agent presets” —— 不是网络问题是 storage key 哈希不一致现象前端 Agent 调用agentpresets/listRPC返回failed to fetch但curl直连正常。根因Substrate 的 storage key 是Blake2_128Concat哈希后的结果而前端 JS 库如polkadot/api默认用xxhash。当 pallet 定义#[pallet::storage] pub type PresetsT StorageMap_, Blake2_128Concat, AccountId, Vecu8;时JS 必须用相同哈希算法才能生成正确 key。诊断# 在节点上用 rpc 调试 curl -H Content-Type: application/json -d {jsonrpc:2.0,method:state_getKeys,params:[0x..., 0x0000000000000000000000000000000000000000000000000000000000000000],id:1} http://localhost:9933 # 查看返回的 keys确认是否为 Blake2_128Concat 格式32 字节前缀修复前端 JS 代码中显式指定哈希算法const presetsKey api.query.agentPresets.presets.key({ accountId: 5GrwvaEF5zXb26Fz9rcQpD6Q681Z19rYUf5DjYyEiGhJLqRc }); // 而不是 api.query.agentPresets.presets.keys()或在 pallet 中改用Twox64Concat更快但不安全需评估风险5.3 问题三“Unable to locate OCI dll” —— Windows 下 OCI 驱动加载失败现象在 Windows 上用 OCI 镜像启动 Substrate 节点报错OCI dll not found但oci.dll明明在 PATH 中。根因Substrate 的sc-executor在 Windows 下默认加载oci.dll但该 DLL 依赖VCRUNTIME140.dll和MSVCP140.dll。若系统未安装 Visual C RedistributableDLL 加载会静默失败。诊断# 用 Process Monitor 监控节点进程过滤 oci.dll 和 LoadImage # 查看失败原因STATUS_DLL_NOT_FOUND 还是 STATUS_DLL_INIT_FAILED修复下载并安装 Microsoft Visual C 2015-2022 Redistributable或在Cargo.toml中禁用 OCI改用wasmi[dependencies.sc-executor] version 0.10.0-dev default-features false features [wasmi]生产环境强烈建议用 Linux 容器规避 Windows DLL 依赖地狱5.4 问题四“Agent execution terminated due to error” —— WASM 内存越界现象Agent 在分析超大合约500KB时崩溃日志显示memory access out of bounds。根因WASM 的 linear memory 初始大小为 1 页64KBsc-executor默认不自动增长。当 Agent 分配内存超过初始大小且未调用memory.grow时访问越界地址会触发 trap。诊断# 启动节点时加 debug 日志 ./target/release/your-node --dev --log sc_executordebug # 查看日志中是否有 grow memory to X pages 字样修复在 pallet 的 WASM 代码中显式调用memory.grow// Rust 中 let new_pages unsafe { core::arch::wasm32::memory_grow(0, 1) }; if new_pages usize::MAX { panic!(memory grow failed) }或在sc-executor配置中设置initial_heap_pages 102464MB但会增加启动内存占用5.5 问题五“Preflight check failed” —— Substrate 节点与 Kubernetes 的健康探针冲突现象将 Substrate 节点部署到 K8sliveness probe 失败Pod 不断重启。根因K8s 的livenessProbe默认用httpGet而 Substrate 的/health端点system_healthRPC在节点刚启动、尚未完成同步时会返回{isSyncing:true}被 K8s 判定为不健康。诊断# 查看 pod 日志 kubectl logs your-node-pod -c node | grep isSyncing # 查看 probe 配置 kubectl get pod your-node-pod -o yaml | grep -A 10 livenessProbe修复修改 liveness probe 为 exec 模式检查进程是否存在livenessProbe: exec: command: - sh - -c - ps aux | grep your-node | grep -v grep initialDelaySeconds: 60 periodSeconds: 30或在 readiness probe 中加入同步状态检查readinessProbe: httpGet: path: /health port: 9933 # 自定义脚本只在 isSyncingfalse 时返回 2005.6 问题六“No such file or directory (os error 2)” —— Off-Chain Worker 文件路径错误现象OCW 尝试读取本地文件/tmp/audit_rules.json报错No such file or directory但文件明明存在。根因OCW 运行在 Substrate 节点的 off-chain worker 线程中其工作目录是节点启动时的current_dir而非/tmp。且容器化部署时/tmp不在容器 volume 中。诊断# 在节点代码
返回列表