首页/新闻资讯/正文详情

Substrate区块链开发框架:从核心概念到自定义链实操

发布时间:2026/9/26 13:29:59 来源:云帆数科 栏目:资讯中心
Substrate区块链开发框架:从核心概念到自定义链实操
我第一次打开 Substrate 的 node-template 时第一反应是这玩意儿到底能干什么后来我才慢慢搞清楚它不是一条现成的链而是一套可以让你把链“拼”出来的开发框架。简单说Substrate 是用 Rust 编写的区块链开发框架目标是把 P2P 网络、共识、存储、Runtime 执行这些底层能力都打包好让你把精力放在业务逻辑上。如果你是想做一条自定义链的开发者或者需要给企业设计存证、溯源、积分系统的架构师再或者只是好奇区块链底层到底是怎么转起来的这篇文章应该能帮你少走很多弯路。Substrate 最打动我的地方是它把“节点”和“Runtime”分得特别清楚。这个划分在很多资料里被一句话带过但其实它就是整个框架的命门。这篇文章我会结合我自己跑通链、写 Pallet、踩坑排错的经验把这个框架从原理到实操拆开讲一遍。1. Substrate 到底是什么一台可以自由组装的“区块链积木机”1.1 为什么它值得学底层能力和业务逻辑被彻底分离很多人第一次接触 Substrate 会误以为它是一个“区块链”其实不是。Substrate 更像是一个“区块链制造工具”。它预先实现了几乎所有区块链都需要的那套机械结构比如节点之间的网络通信、交易广播、区块生成、交易执行、状态存储、RPC 接口这些能力你开箱即用。你真正要写的是“状态怎么变”的那部分逻辑。这里有个关键术语状态转换函数。区块链本质上是一个分布式状态机每个区块都是一次确定性的状态转换。Substrate 把这套状态机拆成两个大块外层是节点服务包括 libp2p 网络、数据库、共识引擎、RPC内层是 Runtime它决定每一笔交易进来之后账户余额怎么变、某个存证哈希是否被记录下来、投票结果如何更新。节点层和 Runtime 层通过 Wasm 接口隔开Runtime 可以被编译成一段独立的 Wasm 字节码甚至能通过链上交易直接替换实现无分叉升级。你可以类比成一辆汽车。底盘、轮子、车身结构是节点层出厂前已经装好方向盘、油门、刹车踏板则是 Runtime 里的各种模块你可以按需求换装。有些车是家用轿车有些是赛车Substrate 允许你改的是驾驶逻辑而不是重新造轮子。从工程角度看这种分离直接改变了开发范式。过去做一条链要先解决网络层怎么组网、共识怎么保证一致性、区块数据存哪里然后才轮到你的业务。现在这些变成了配置项和默认依赖你只需要关心状态转换。这也是为什么很多团队把 Substrate 当成区块链领域的 Spring Boot——它不是一个框架而是一整个生态体系的起点。1.2 三条路线对比从零写、分叉、用 Substrate 框架我见过不少团队在立项时都会纠结要不要从零写一条链还是分叉一条现成的链还是用 Substrate。我直接把三种路线的差异放在一张表里。对比维度从零编写分叉现有链使用 Substrate开发成本极高网络、共识、存储全要自己搞起步快但删功能比写功能更难中等底层已被框架覆盖可控性最高每一行代码都可控受原链设计约束较大高Runtime 可以完全自定义升级方式需要自己设计通常要做硬分叉依赖原链的升级机制迁移麻烦无分叉 Runtime 升级替换 Wasm 即可团队要求需要懂 P2P、加密、共识、存储的复合团队需要吃透原链所有逻辑重点掌握 Rust 与 Substrate 抽象适合场景学术研究、特殊共识实验只想快速改币种参数的需求定制链、企业链、公链原型从零写的优势是没有任何历史包袱但代价是你需要维护密码学、网络协议、共识算法这些非常容易出错的组件。分叉现成链看似能快速上线可一旦你想改掉原有链的某些根深蒂固的假设反而会被原链的设计反向绑架。我见过一个项目分叉比特币源码想做一个带实体资产的平台结果光是移除 UTXO 模型就花了两个月。Substrate 的定位是底层通用能力已经实现并且经过大量链验证你在一个相对稳定的骨架上做定制。它不为业务逻辑设限只约束“区块链基础设施”那一层。这个取舍对大多数想把业务跑在链上的团队来说更经济。1.3 谁适合用典型场景和人群适合用 Substrate 的场景我总结下来大概有四类。第一类是联盟链或企业内部系统比如供应链溯源、存证、积分、卡券。联盟链不需要完全公开的 PoW 共识通常用固定验证人列表和 Aura 共识就能满足需求Substrate 正好可以通过模板快速配置出来。第二类是公链原型验证比如你想验证一个社区币的经济模型、一个去中心化治理机制、一个 NFT 市场的撮合逻辑不需要先搭网络直接写 Runtime。第三类是学术和研究项目比如测试新的共识算法、新的存储结构Substrate 允许你替换共识引擎和 Runtime 实现。第四类是已经有中心化系统想逐步增加“可信记录”能力的团队可以先跑一条存证链把关键操作哈希上链与现有系统并行。人群方面有 Rust 基础当然最顺但即使你只会一点 Python 或 Go也能按模板先把链跑起来。Substrate 模板项目给你一条可运行的链你在上面改业务模块学习曲线比想象中平滑。前提是你要接受 Rust 类型系统和宏编译报错带来的挫败感。不过这种挫败感在第一次链上交易成功时会瞬间变成很强的成就感。2. 核心概念拆解Runtime、FRAME 和 Pallet 是如何协作的2.1 Runtime链上逻辑的“操作系统”在 Substrate 语境里Runtime 是一切的中心。它编译出的 Wasm 字节码会被嵌入区块并成为链的一部分。每个验证节点在验证区块时实际上是在本地的 Wasm 沙箱里执行同一份 Runtime 代码。因为代码是确定性的所以每个节点最终会得到相同的状态根。Runtime 要处理的数据主要有几类账户、余额、存储数据、事件日志、区块头和外部输入。外部输入就是 Extrinsic包括用户提交的交易和节点产生的内在操作。一条链的业务规则全都在 Runtime 中定义。比如转账时的手续费怎么算、单笔转账上限是多少、某个角色是否有权限修改参数这些都在 Runtime 的 Pallet 里实现。我建议新手先把“节点”和“Runtime”这两个词分开记忆。节点是那台一直在跑的进程负责接收网络消息、打包区块、存储状态、提供 RPCRuntime 是这台进程要执行的“内核”可以被替换。你可以把 Runtime 升级想象成手机系统热更新不用换手机只要把系统镜像替换掉手机就变成了新系统。而这条链的历史状态会保留下来账本不会断。2.2 FRAME把常用功能做成标准件FRAMEFramework for Runtime Aggregation of Modular Entities是 Substrate 为开发 Runtime 提供的一套模块化框架。它包含一系列宏和工具比如construct_runtime!、#[pallet]、frame_system、frame_support用来把不同的 Pallet 组装成一个整体。你可以把 FRAME 理解成预备好的标准件仓库。frame_system提供最基础的账户、区块头、交易索引能力pallet_balances提供余额和转账pallet_timestamp提供链上时间pallet_sudo提供超级管理员权限。这些标准件是大多数链都需要的直接复用即可。FRAME 的另一大贡献是自动生成大量样板代码。存储结构、事件枚举、调用分发、错误类型都可以通过宏自动生成。这让 Runtime 代码看起来非常精简但也带来了一个副作用宏报错信息经常很反直觉。遇到这种错误时不要慌优先看宏展开后的代码或者回退到官方模板的同版本代码逐个比对差异。2.3 Pallet写业务就是写 PalletPallet 是 FRAME 的基本功能模块也是开发者真正写业务的地方。一个 Pallet 通常由几个部分组成配置接口 Config、存储项 Storage、调用入口 Call、事件 Event、错误类型 Error、权重 Weight。你可以为存证写一个 Pallet为票据写一个 Pallet为投票写一个 Pallet。链上的业务复杂度最后都会分解成一个个 Pallet 的组合。官方生态里已经有不少现成 Pallet比如pallet_assets管理多种资产、pallet_contracts支持在链上部署 Wasm 智能合约、pallet_identity做链上身份。开发新链时优先看看有没有现成 Pallet 可以组合不要什么都自己写。组合出来的系统比自研的更容易维护因为官方 Pallet 经过大量测试。但要注意Pallet 之间是有耦合关系的。比如你要用pallet_balances就需要先实现frame_system的账户体系你要用pallet_assets又需要pallet_balances的支持。这种依赖关系通过 Rust 的 trait 约束表现如果漏了某个 impl编译时会给你一个很长的类型约束错误。解决办法是先跑通最小的 Runtime再逐步加模块。2.4 共识与网络层出块、最终性、P2PSubstrate 的共识并不是一个黑盒而是可以配置和替换的。最常用的组合是 Aura Grandpa。Aura 负责出块规则是验证人轮流生产区块每一轮由一个验证人负责出块Grandpa 负责最终性验证人对区块进行投票当超过阈值投票完成时区块链支点就不容易被回滚。也有一些链会使用 Babe 替代 AuraBabe 是随机插槽出块通过 VRF 在每个插槽中选出出块人更适合验证人较多的公链场景。出块和最终性分开设计的好处是出块可以快最终性可以慢一点两者互不阻塞。这和你写关系型数据库时把“事务提交”和“日志持久化”分开考虑类似能带来更好的扩展性和可调试性。网络层则完全封装在节点层里基于 libp2p 实现节点发现、加密传输、消息广播。开发期间你基本不用碰底层网络只需要知道链的节点之间通过多端地址互相连接链在开发模式下会自动分配端口和 peer 信息。真正要自己改网络协议的情况极少除非你在做底层性能优化或自定义传输层。2.5 升级能力Runtime 为什么能无分叉升级区块链升级一直是个大痛点。传统链改一条规则通常需要社区投票、全节点升级、硬分叉处理不好就分裂成两条链。Substrate 通过把 Runtime 代码编译成 Wasm 并存在链上绕开了这个难题。任何人提交新的 Runtime Wasm 代码经过治理流程审批后链上状态就切换到新版本。这里的关键是 Wasm 沙箱。老合约在新合约切换后依然能确定性地执行状态存储也可以跨版本保留。升级过程可以在运行时完成不需要停止出块因此叫无分叉升级。但无分叉不等于“无痛”。如果新 Runtime 改变了存储格式你必须写迁移逻辑否则旧数据可能会被错误解析。存储迁移是我见过最多人踩坑的地方后面我会专门讲。3. 实操用 Substrate 模板搭一条带存证功能的自定义链3.1 环境准备Rust 和 Wasm 目标先准备 Rust 环境。如果你还没有安装 Rust用 rustup 安装然后切换默认工具链到 nightly并添加 wasm32-unknown-unknown 目标。curl https://sh.rustup.rs -sSf | sh rustup default nightly rustup target add wasm32-unknown-unknown注意Substrate 的 Runtime 编译成 Wasm 时依赖 Rust 的 nightly 特性所以不能用 stable。不同版本的 Substrate 对 nightly 版本也有要求模板仓库里通常有一个 rust-toolchain 文件来锁定版本。我建议你跟随模板的锁定版本不要自己升级到最新 nightly否则很容易遇到 Rust 特性变化导致的编译失败。在 Ubuntu 类的系统上还需要提前装好系统依赖包括 build-essential、clang、cmake、libssl-dev、pkg-config。这些是编译 RocksDB 和 Wasm 工具链时需要的。如果你在 Mac 上开发通常不需要额外装太多但最好先安装 Xcode Command Line Tools。sudo apt install -y build-essential clang cmake libssl-dev pkg-config这里要提醒一句第一次编译 Substrate 项目的时间会比较长全量编译可能要十几分钟甚至更久取决于机器性能。这不是程序卡住了而是依赖树非常大。建议编译时开一个单独的终端用cargo build --release慢慢跑中间不要 CtrlC让 Cargo 缓存后续复用。3.2 从模板生成节点官方提供了一个最常用的上手模板substrate-node-template。它包含一个可以运行的节点、一个默认的 Runtime 和一个示例 Pallet。克隆下来后你得到的是一条最小可用链。git clone --depth 1 https://github.com/substrate-developer-hub/substrate-node-template.git cd substrate-node-template模板结构大概是这样的node节点客户端代码包括 CLI、RPC、P2P 配置。runtimeRuntime 源码包括 construct_runtime! 宏里面注册的所有 Pallet。pallets业务模块目录默认有一个 template 示例。scripts一些帮助脚本。打开runtime/src/lib.rs你会在construct_runtime!宏里看到默认注册的 Pallet。这个宏看起来像配置清单但实际上是代码生成器。它把 Pallet 名称映射到 Rust 模块并生成大量支撑代码。如果你想新增一个 Pallet这里必须同步注册否则链上不可用。模板默认使用--dev开发模式本地跑是免权限的。Alice 和 Bob 这些预置账号都有余额你可以直接拿来测试。模板里还带了一个前端模板叫 substrate-front-end-template方便你通过网页连接节点。3.3 写一个存证 Pallet我来演示一个最常见的业务存证。用户可以提交一段数据的哈希链上记录“谁在什么时间提交了哪个哈希”之后可以验证。只存哈希不存原文既节省链上空间也避免把敏感业务数据直接公开。下面是一个简化版的 Pallet 代码基于新版本的 FRAME 宏。不同 Substrate 版本之间宏有细微差异你以当前模板中的实际写法为准但核心结构是稳定的。use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; #[frame_support::pallet] pub mod pallet { use super::*; #[pallet::pallet] #[pallet::without_storage_info] pub struct PalletT(_); #[pallet::config] pub trait Config: frame_system::Config {} #[pallet::storage] #[pallet::getter(fn claims)] pub type ClaimsT: Config StorageMap_, Blake2_128Concat, T::AccountId, T::Hash, OptionQuery; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum EventT: Config { ClaimCreated(T::AccountId, T::Hash), ClaimRevoked(T::AccountId, T::Hash), } #[pallet::error] pub enum ErrorT { AlreadyClaimed, NoSuchClaim, } #[pallet::call] implT: Config PalletT { #[pallet::weight(10_000)] pub fn create_claim(origin: OriginForT, claim: T::Hash) - DispatchResult { let who ensure_signed(origin)?; ensure!(!Claims::T::contains_key(who), Error::T::AlreadyClaimed); Claims::T::insert(who, claim); Self::deposit_event(Event::ClaimCreated(who, claim)); Ok(()) } #[pallet::weight(10_000)] pub fn revoke_claim(origin: OriginForT) - DispatchResult { let who ensure_signed(origin)?; ensure!(Claims::T::contains_key(who), Error::T::NoSuchClaim); let claim Claims::T::take(who); Self::deposit_event(Event::ClaimRevoked(who, claim)); Ok(()) } } }解释几个关键点。Claims是一个 StorageMapkey 是账户地址value 是哈希。Blake2_128Concat是存储 key 的哈希方式它有很好的均匀分布性又可以反向读取完整 key。OptionQuery表示查询不到时返回 None方便前端判断。create_claim会先确认调用者是链上签名账户再检查该账户是否已经提交过哈希防止重复。revoke_claim则是撤销自己的存证。整个逻辑非常简单但它已经具备一个业务模块的完整骨架有存储、有校验、有错误、有事件。在实际项目中你通常会根据业务需要调整存储结构比如不只是单账户对应单哈希而是一个哈希可以对应多个账户或者记录提交时间。你可以在 StorageValue 或 StorageMap 之外使用 StorageDoubleMap也可以把块高度存进值里这些都不难换来的是更贴合业务的存储结构。3.4 把 Pallet 装进 Runtime写完 Pallet 后还需要把它注册到 Runtime。首先在runtime/Cargo.toml中添加依赖。模板中默认已经引入了 pallet-template所以如果你是直接改模板 Pallet这一步可以跳过但如果是新建 Pallet需要手动加。pallet-template { path ../pallets/template, default-features false, features [std] }然后在runtime/src/lib.rs中注册 Pallet。在 construct_runtime! 宏里添加一行TemplateModule: pallet_template,同时还需要实现它的 Config trait。由于我们定义的 Config 没有额外的关联类型通常这样写impl pallet_template::Config for Runtime {}这里最容易被忽略的是 Cargo.toml 里的 std feature 传递。Runtime 编译成 Wasm 时是不能有标准库的所以 Pallet 默认使用 no_std。而链上运行时又需要使用 std 做测试。正确做法是 Pallet 的 Cargo.toml 里要有[features] default [std] std [frame-support/std, frame-system/std]这样模板在构建 Wasm 时关闭 std在本地测试时开启 std。很多新人第一次编译报一堆“the trait bound was not satisfied”的错误基本都是这里漏了 feature 传递。3.5 编译、启动和手工验证在项目根目录执行cargo build --release编译成功后用开发模式启动节点./target/release/node-template --dev --tmp--dev表示开发模式使用固定的 Alice 等预置账号--tmp表示临时数据目录退出后数据即销毁适合测试。启动后你会看到不少日志其中最关键的是 JSON-RPC WebSocket 地址默认是 127.0.0.1:9944。验证方式有两种。第一种是直接用命令行通过一个简单的脚本调用节点。不过没有 web 页面直观。第二种是把节点连到 Polkadot JS Apps 网页端。打开浏览器进入 polkadot.js.org/apps添加自定义节点填入 ws://127.0.0.1:9944连接后就能看到链的信息。然后切到“开发者 - 交易”选择我们的 Pallet 调用 createClaim填一个哈希值。提交后在“开发者 - 链状态”里选择 Templates 模块的 claims 存储就能看到刚刚写入的记录。我第一次在这个界面看到自己的自定义存储时还是有点兴奋的。因为从“写代码”到“链上状态”的距离在这一刻被压缩得极短。前端调用在后端 Pallet 里执行最终写入链上存储整个过程就是一个状态转换函数的实例。这里也要补充一个小提醒。#[pallet::weight(10_000)]只是开发阶段的临时手法代表这个调用消耗 10000 个 weight 单位生产链上不能这样写。真实项目的 weight 需要通过 benchmark 生成否则手续费估算和出块时间预算都会失控。我会在后面的常见问题里再说。4. 常见问题与排查技巧实录4.1 编译失败的几类典型原因Rust 编译 Substrate 项目报错信息经常又臭又长但绝大多数情况归纳下来就几个原因。链接器错误例如linker command failed with exit code 1。这通常不是代码问题而是系统缺少 clang、cmake 或 libclang-dev。特别在使用 RocksDB 相关依赖时系统库缺失会直接中断链接。解决方案是装好系统包后重新编译不用删 Cargo 缓存。用了错误的 Rust 工具链。Substrate 的 Runtime Wasm 目标只支持 nightly如果你用 stable 编译会看到can only be compiled with the nightly toolchain。用 rustup 切换工具链或者依赖模板中的 rust-toolchain 文件自动切换。宏语法版本不对。FRAME 宏更新很频繁不同版本的模板写法不一样。如果你复制了一段网上老教程的代码贴到新模板经常会报找不到类型或 trait。解决方案不是硬调代码而是打开当前模板里的 pallet 源码和新代码逐行对照。新版模板的宏结构比旧版清晰很多但细节差异仍然存在。编译 Wasm 时内存不足。Substrate 的全量编译非常吃内存尤其是 linking wasm 时老电脑很容易出现 OOM。关闭其他软件或者把 Cargo 的并行编译数调低cargo build --release -j 2。如果你在容器里编译确保 Docker 内存限制给到至少 8GB。4.2 Runtime 升级时的存储迁移问题无分叉升级很香但很多人只注意到了功能升级没注意到存储变化。当新 Runtime 引入一个新的存储项或者改变了某个字段的含义时已经存在于旧区块状态里的数据并不会自动“具象”成新格式。如果新代码按新的格式去读旧数据轻则读到默认值重则逻辑错乱。解决方法是使用 pallet 的存储版本机制和迁移函数。每个 Pallet 可以记录一个 StorageVersion当版本号变化时在执行升级时运行迁移逻辑。迁移逻辑写在 pallet 的 Hooks 实现里供 Runtime 在 setCode 时调用。#[pallet::hooks] implT: Config HooksBlockNumberForT for PalletT { fn on_runtime_upgrade() - Weight { if StorageVersion::get::PalletT() ! CurrentVersion as Getu16::get() { // 执行旧数据迁移 StorageVersion::put::PalletT(CurrentVersion as Getu16::get()); } T::DbWeight::get().reads_writes(1, 1) } }我只给出骨架实际迁移逻辑根据业务千差万别。但原则是一样的先在测试网上跑升级导出旧状态并检查新状态再上主网。链上数据不像普通数据库有 undo 日志一旦迁移函数写错了可能造成不可逆的损坏。建议上线前准备一个“回滚用 Runtime”如果迁移出问题马上用 setCode 切回旧版本。这个过程我现在每次做升级都会准备一遍虽然看起来笨但能救命。4.3 前端读取节点数据时的顺序问题用 Polkadot JS 连接节点时很多人会遇到存储读不到预期值的情况。一种常见原因是查询区块高度不对。存储查询默认读取当前最佳区块的状态而如果你刚提交了一笔交易但它还处于 pending 状态存储里当然查不到。这时候应该先等待交易被包含进区块或者使用订阅接口监听区块头更新后再查询。另外一个坑是事件过滤。通过 system.events 可以拿到当前区块的所有事件但你需要过滤出自己 Pallet 的 Event 枚举。因为所有 Pallet 共享同一个事件 Topic如果业务里用了多个 Pallet事件枚举会嵌套多层过滤时要注意解包。前端代码里我一般用api.events.custom.claimCreated这种类型安全又有可读性的接口。如果直接手写events.filter去匹配裸对象很容易在字段结构变化时踩空。还有一个小技巧。启动节点时加上--rpc-external或调整 RPC 监听端口会影响前端连接。默认情况下节点只监听 localhost如果你在远程服务器上跑节点前端连不上不要怀疑 API 代码先检查节点的 RPC 监听地址和防火墙。4.4 关于性能和确定性的提醒Substrate Runtime 是确定性的这意味着同一个区块在任何节点执行结果都必须完全一致。因此你不能在 Pallet 里使用系统时间、随机数、环境变量、文件读取这类无法保证一致的操作。链上时间要通过pallet_timestamp的 Now 获取随机性要通过 VRF 或链上可验证的随机数接口获取。很多人在这里栽过跟头。比如想写一个游戏 Pallet直接用了std::time::SystemTime::now()生成随机奖励本地测试时一切正常一旦多节点运行不同节点计算出不同结果共识直接崩掉。这种问题很难排查因为它不是编译错误而是运行一致性错误。所以从一开始就要把“确定性”当成编码纪律。性能方面Pallet 里不要写过于复杂的循环更不要把大列表一次性加载到内存做遍历。链上存储的每次读写都有成本复杂的存储结构会让手续费不可控。我通常的做法是先在链下把计算做完把结果作为交易参数提交上来链上只做校验和落库。这样既保证安全又减少链上计算压力。权重系统也值得重视。weight 是 Substrate 衡量交易消耗的机制节点会统计一个区块内所有交易的 weight然后按最大区块 weight 出块。如果权重标注远小于实际执行成本区块可能装进太多重交易导致出块超时。生产环境一定要用 benchmark 生成 weight而不是我上面示例里固定的 10_000。5. 实操心得与后续扩展方向5.1 三条过来人的经验我亲手跑通过好几种自定义链也折腾过不少模块。如果让我总结三条最值得记住的经验第一是“先跑通最小闭环再加复杂度”。不要第一天就想把什么资产系统、合约系统、治理系统全部塞进去。先用模板跑一条只有 System、Balances 和你自己的业务 Pallet 的链确认账户、转账、事件都能正常工作再逐步引入其他模块。第二是“严格跟随官方模板版本”。Substrate 的迭代速度很快网上很多资料是半年之前的放到当前版本可能编译不过。你应该以官方仓库的当前模板为锚点代码和依赖版本都尽量对齐。自己手工升级 Substrate 依赖尤其是跳过多个大版本时会陷入无穷无尽的宏报错里那种痛苦我太熟悉了。第三是“养成写测试的习惯”。Pallet 可以用 Rust 单元测试直接构造外源、调用函数并检查存储结果。运行cargo test -p pallet-template就能跑。测试不用多复杂但至少覆盖正常提交、重复提交、错误账户撤销这几个核心路径。因为 Runtime 改动频繁有测试兜底升级时才不至于把已有功能改崩。5.2 还能往哪些方向扩展跑通一条最小链之后扩展方向基本是开放的。如果你想支持智能合约可以集成 pallet-contracts。它会给你的链提供 Wasm 合约执行环境用户可以上传合约代码、实例化、调用合约。这会增加 Runtime 复杂度但如果你希望链具备可编程性这是必经之路。如果你想做资产或 NFT可以研究 pallet-assets。它支持在一条链上创建多种自定义资产每种资产可以有自己的管理员、冻结列表、元数据。配合你的业务 Pallet可以实现完整的交易流转。如果你想做链上治理可以引入 pallet-democracy 和 pallet-treasury。代议制投票、公投、国库支出都能直接复用不需要自己从零设计一套治理框架。如果你想把链接入更完整的开发者工具可以研究 Substrate API Sidecar 和 subxt。前者提供 REST 风格 API后者是 Rust 的原生 Substrate 客户端适合后端服务直接读取链上数据。在我个人的实际体验里Substrate 的学习曲线最陡的地方不在概念而在“宏生成代码”和“版本变化”这两座山上。但只要你把一条最小链跑通后面其实是不断拼装 Pallet 的过程。真正决定链好不好用的永远是你对业务状态转换模型的思考深度而不是工具本身。

相关推荐

网文作者AI工具横评:蛙趣拼文、Claude、DeepSeek、Gemini、豆包配 TaoToken 统一接入实测
网文作者AI工具横评:蛙趣拼文、Claude、DeepSeek、Gemini、豆包配 TaoToken 统一接入实测

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 13:29:53

端侧AI部署利器:INT8模型量化原理与实战技巧
端侧AI部署利器:INT8模型量化原理与实战技巧

聊端侧AI,绕不开一个词:模型量化。这一篇是端侧AI入门笔记系列的第四篇,就从浮点格式和INT8的差别讲起。如果你做过端侧AI硬件部署,一定遇到过类似画面:PC上跑得好好的FP32模型,挪到手机、开发板、智能摄像… · 2026/9/26 13:29:53

VideoGen-Agent实战:强化学习驱动的视频生成智能体系统
VideoGen-Agent实战:强化学习驱动的视频生成智能体系统

1. 从“能生成”到“会生成”:VideoGen-Agent 到底在解决什么问题视频生成模型这两年进步飞快,输入一段文字就能吐出几秒钟的画面,这件事本身已经不算稀奇。但真正上手做过视频生成项目的人都知道,单次生成的质量和“完成一个视频… · 2026/9/26 13:29:53

OpenClaw imageModel 配置指南:TaoToken 统一 Key 接入与 settings.json 骨架
OpenClaw imageModel 配置指南:TaoToken 统一 Key 接入与 settings.json 骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 14:04:31

国产算力全线兑现背后:用 TaoToken 统一 Key 打通 AI 编程工具链的 config.toml 骨架
国产算力全线兑现背后:用 TaoToken 统一 Key 打通 AI 编程工具链的 config.toml 骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 14:04:31

Spring Boot+大数据驱动:海河沿岸城市双修景观画像系统设计实践
Spring Boot+大数据驱动:海河沿岸城市双修景观画像系统设计实践

做毕设做到“大数据 Spring Boot”这个组合,大多数人第一反应是:这俩怎么凑一块?再往下看,还有“海河沿岸城市双修景观画像系统”,又冒出来一个城市规划领域的词。实际上,这套项目拆开来看就三件事&#x… · 2026/9/26 14:04:24

从零掌握 AI Skill 编写:Markdown 文件结构、调试与实战技巧
从零掌握 AI Skill 编写:Markdown 文件结构、调试与实战技巧

1. 从零理解 Skill 到底是什么很多人第一次听到 Skill 这个词,脑子里浮现的是游戏里的技能树,或者是某个插件市场里可以一键安装的功能包。但如果你真正动手写过、改过、调试过 Skill,就会发现它更像是一份写给 AI 的"岗位说明书"—… · 2026/9/26 14:04:18

Allure测试报告实战:从pytest到CI的质量可视化与团队复盘
Allure测试报告实战:从pytest到CI的质量可视化与团队复盘

自动化测试做到一定程度,大家拼的其实不是脚本写法,而是结果表达能力。我经历过无数个这样的早晨:昨晚流水线跑完一千多条用例,第二天全组人在CI控制台前翻输出,却没人能立刻说清楚到底挂了几条、挂在哪。Allure报告正… · 2026/9/26 14:04:18

AI辅助编程工具详细介绍:用 TaoToken 统一 Key 打通 Cline 与 CC Switch 配置
AI辅助编程工具详细介绍:用 TaoToken 统一 Key 打通 Cline 与 CC Switch 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 14:04:18

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21

OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40

向下兼容与向上兼容:接口设计中的兼容性策略与工程实践
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践

一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码