ARTICLE DETAIL

资讯详情

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

Substrate区块链开发框架:从原理到实操的自定义链构建指南

Substrate区块链开发框架:从原理到实操的自定义链构建指南 第一次看到 substrate 这个词我下意识以为是材料学里的“衬底”一种让上面那层东西站得稳的基础材料。后来在区块链开发圈里再见到它才发现它是同一类逻辑的工程化身你不想从零搭一条链就把共识、网络、存储这些底层全部交给它自己在上面专心做业务逻辑这就是 Substrate。它不是一条已经跑起来的链而是一套造链框架帮你把一条区块链的“基础设施”提前做完你只需要关心自己的业务模块怎么设计。这篇文章我会从原理讲到实操再讲一讲我踩过的坑适合想快速了解 Substrate 是什么、想动手跑一条自定义链、或者正准备用 Substrate 写 pallet 的人。如果你完全没接触过区块链开发也不用担心我会把关键概念都掰开揉碎讲清楚。1. 先说清楚Substrate 到底是一条链还是一个框架1.1 一条“链”和一个“造链框架”的区别很多人第一次看到 Substrate 都会有一个误解它是不是像比特币、以太坊那样是一个可以直接参与、直接挖矿或直接转账的网络不是。比特币和以太坊本身已经是一条运行中的链它们有主网、有代币、有一套固定的规则。Substrate 更像一个“造链工具箱”你用它做出来的东西才是链。打个比方以太坊是一栋已经装修好、住进去就能营业的房子而 Substrate 是毛坯房加全套装修队你可以决定哪里是承重墙、哪里放厨房、哪里做开放式书房。这个区别非常关键。直接用 Substrate你不一定要保留以太坊那样“全球通用合约平台”的形态。你可以做一条只处理溯源数据的链也可以做一条内部结算链甚至可以做一条没有代币的联盟链。Substrate 提供的是共识机制、网络层、交易池、数据库存储这些通用件而你的业务逻辑写在上面的 pallet 里相当于把“链”变成了可拼装的模块化产品。在区块链开发里之前大部分团队要做自定义链通常走的是“分叉”路线把比特币或以太坊的源码复制一份改参数改规则。这样做短期内可以跑但是一旦上游代码升级或者你想调整核心逻辑改动成本会高得吓人。Substrate 的思路不是让你去改一套“别人的代码”而是把链的公共部分抽象出来让它们变成标准化零件这也是为什么它会被叫做“区块链开发框架”。1.2 Substrate 解决的是哪一类问题你可以用一句话理解 Substrate 的定位如果你需要一条链但不想从账本、P2P 网络、共识算法、账户系统全部自己写那你应该用 Substrate。我见过不少团队一开始信心十足想用 Go 从零写一条链。结果三个月后网络层的节点发现、交易广播、区块同步、账户签名验证、存储快照每一样都能耗掉一个工程师的全部精力。等到终于能跑起来又发现共识稳定性完全没法保证。Substrate 把这些底层东西都封装好了它已经自带 libp2p 网络、RocksDB/ParityDB 存储、BABE/Aura 出块共识、GRANDPA 最终性共识以及一套标准的账户与签名体系。当然用了 Substrate 不等于你不需要理解底层。它只是让你在起步阶段不用关心这些细节但在需要调优、排查问题时你依然要能看懂底层日志和运行机制。换句话说Substrate 降低的是“从零到能够跑通”的门槛而不是“不需要懂区块链”的门槛。还有一点值得注意Substrate 做出来的链天然支持“无分叉升级”。传统区块链要升级规则一般得让全网节点一起改代码如果节点不配合就可能分叉。Substrate 的运行时是存在链上的 Wasm 代码节点客户端可以在不出岔子的情况下升级这一点在企业应用和需要快速迭代的场景里非常香。2. 核心原理为什么 Substrate 能“优雅地造链”2.1 Client 和 Runtime 的分离理解 Substrate 的第一个关键点是它把一条链分成了“Client客户端”和“Runtime运行时”两个层次。Client 负责那些比较“物理”的事情网络通信、同步区块、存储数据、执行 Wasm 代码。Runtime 则负责链的“精神状态”账户余额怎么变、交易是否有效、区块里的每一笔状态转换如何处理。在 Substrate 里Runtime 会被编译成两套结果一套是原生机器码方便本地快速执行和调试另一套是 Wasm 字节码会作为链的一部分保存下来作为链上执行的标准。为什么这么设计因为 Client 可以理解成一台电脑的操作系统Runtime 理解成跑在操作系统里的业务程序。操作系统即使保持不变上面的业务程序也可以热更新。放到链上就是只要修改 Runtime 并提交一次升级交易节点会自动加载新的 Wasm 代码旧客户端依然能识别新规则不需要所有节点强制停机升级。我对这种设计的第一反应是“优雅”但实际使用中也会带来思考问题因为你随时可以改 Runtime所以“治理”就变得很重要。谁来发起升级、多少人同意才生效这些最好在链设计之初就定下来否则无分叉升级会成为一把没有保险栓的枪。2.2 FRAME把链功能拆成可插拔模块Runtime 本身不能是杂乱无章的一堆代码所以 Substrate 提供了一个叫 FRAME 的模块化体系。FRAME 的全称是 Framework for Runtime Aggregation of Modular Entities你可以把它理解成一个“pallet 收纳架”。pallet 就是模块比如账户余额管理是一个 pallet资产发行是一个 pallet合约执行是一个 pallet甚至 NFT 也是一个 pallet。每个 pallet 负责一组独立的功能彼此之间可以通过接口调用。用 FRAME 搭链的过程特别像你在选配一台电脑主板是 RuntimeCPU、内存、显卡是不同 pallet你喜欢哪个功能就把对应 pallet 插上去。不想用传统账户模型可以不挂 pallet_balances。想支持多种资产挂上 pallet_assets。想允许用户部署智能合约挂上 pallet_contracts。节点模板里默认带了一组常用 pallet比如系统模块 pallet_system、余额模块 pallet_balances、交易费模块 pallet_transaction_payment。这些模块已经经过了大量生产环境检验你在上面写业务时不用重复造轮子。FRAME 还允许每个 pallet 定义自己的存储、事件、错误类型和外部的可调用函数这相当于把业务领域模型直接嵌入到链的运行逻辑里。2.3 共识、网络、存储这些“基础设施”是怎么被藏的很多第一次接触 Substrate 的人会困惑我的链到底怎么出块谁来当验证人这里其实不需要你一开始就把所有机制都定制成“业内最优”节点模板默认跑的是单节点开发模式用 Aura 或 BABE 出块配合 GRANDPA 做最终性确认。Aura一个简单的轮流出块共识适合开发测试环境。BABE类似随机 slot 的权益证明出块共识适合正式网络。GRANDPA最后确认共识它不一定参与出块但负责给区块“盖棺定论”让链上状态最终不可回滚。你可以把共识理解成“一群人怎么轮流写账本”的规则。Substrate 把这些共识都封装成了可选的模块你只需要在 chain spec 里指定用哪种方案。存储方面默认是本地 key-value 数据库每个账户状态、每个链上数据都存在里面节点间同步时只同步最终确认的区块效率还算不错。对于普通业务开发者来说最爽的一点是网络层不用管了。节点发现、连接维护、区块广播、交易广播全是 libp2p 在处理。你写 pallet 时只需要关心“状态怎么变化”不需要关心“状态变化怎么传播到全网”。3. 实操从零跑起一条 Substrate 开发链3.1 开发环境准备Rust 工具链Substrate 是用 Rust 写的所以第一步是准备 Rust 环境。无论你之前用不用 Rust这里有几个基础要求操作系统建议 Linux 或 macOSWindows 用户建议用 WSL2纯 Windows 下编译 Wasm 会遇到不少环境坑。内存建议至少 8GB推荐 16GB。第一次编译要链接大量 crate内存太小容易 OOM。硬盘建议留出 20GB 以上空间target目录会非常占地方。安装 Rust 直接用官方工具链管理器curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh装完后把工具链切到 stable并添加 Wasm 编译目标rustup default stable rustup update rustup target add wasm32-unknown-unknown --toolchain stable为什么必须要加wasm32-unknown-unknown因为 Substrate 的 Runtime 需要编译成 Wasm存到链上。少了这个 target后续构建会直接报错。3.2 用 node-template 初始化项目Substrate 官方建议新手从 substrate-node-template 开始。它不是一个空项目而是已经配置好了一条最小可用链包含基本账户、余额转账、节点启动、RPC 服务你只需要克隆下来编译。git clone --depth 1 https://github.com/substrate-developer-hub/substrate-node-template cd substrate-node-template如果你要起一个正式项目我建议先把这份模板完整读一遍尤其是runtime/src/lib.rs里的construct_runtime!宏那里能看到整条链由哪些 pallet 组成。模板里已经默认挂上了pallet_balances、pallet_sudo等模块方便你做管理操作。3.3 编译并启动开发链编译是一条比较磨人的工序尤其第一次构建要下载和编译几百个依赖。建议直接用 release 模式虽然编译时间更长但跑起来性能和稳定性都好得多。cargo build --release如果机器内存不大可以用环境变量限制并行编译任务避免卡死CARGO_BUILD_JOBS4 cargo build --release编译完成后目录下会生成一个target/release/node-template可执行文件。启动开发链只需要加一个--dev参数./target/release/node-template --dev这里解释一下参数含义--dev以开发模式启动自动生成一个临时链会有预置的代币余额并且默认允许单节点出块。--tmp使用临时数据目录退出后链上数据清空适合反复做实验。--alice指定节点身份为 Alice这是开发常用的测试账户。启动后你会看到节点开始产生区块日志里会出现Prepared block for proposing at 1之类的内容。这说明你的链已经跑起来了。3.4 和链交互一个最基本的转账测试只看到区块产生还不够最好实际发一笔转账验证整条链路是否通畅。最直接的方式是使用官方前端模板它会连接你本地的ws://127.0.0.1:9944节点并提供转账、余额查询、账户创建等功能git clone https://github.com/substrate-developer-hub/substrate-front-end-template cd substrate-front-end-template yarn install yarn start打开浏览器进入前端页面后选择一个测试账户比如 Alice向 Bob 转一笔 token。如果前端页面显示转账成功并且 Alice 的余额减少、Bob 的余额增加就说明节点、Runtime、账户系统、交易池全链路都通。这一步也会让你更直观地理解Substrate 做出来的链并不是一个“区块浏览器里的抽象物”它就是一套有真实交易、真实状态变化的系统。你后续的所有业务本质上都是在扩展“状态变化”这件事。4. 再加一个自定义模块pallet 开发初体验4.1 pallet 的基本结构链能跑通只是开始真正有意思的是写自己的 pallet。一个 pallet 就是一个 Rust 模块通常由这几部分组成Configtrait定义这个模块需要哪些外部类型或接口。Pallet结构体模块本体。Storage链上存储的数据。Event模块触发的事件。Error模块可以返回的错误。Call外部可以调用的函数也就是“链上交易/操作”。你可以把 pallet 理解成后端服务里的一个微服务只是它不是一个 HTTP 接口而是一套链上函数。每次调用这些函数如果成功就会更新链上状态并产生事件。4.2 最小可用的自定义 palletnode-template 里已经自带了一个pallet_template可以直接在这个基础上改。以一个最简单的“存一个数字”的模块为例核心代码大致是这样#[frame_support::pallet] pub mod pallet { use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; #[pallet::config] pub trait Config: frame_system::Config { type RuntimeEvent: FromEventSelf IsTypeSelf as frame_system::Config::RuntimeEvent; } #[pallet::pallet] pub struct PalletT(_); #[pallet::storage] #[pallet::getter(fn something)] pub type SomethingT StorageValue_, u32, ValueQuery; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum EventT: Config { SomethingStored(u32, T::AccountId), } #[pallet::call] implT: Config PalletT { pub fn set_something( origin: OriginForT, something: u32, ) - DispatchResult { let who ensure_signed(origin)?; Something::T::put(something); Self::deposit_event(Event::SomethingStored(something, who)); Ok(()) } } }这段代码的核心逻辑很简单用户调用set_something传入一个数字数字被写入链上存储同时触发一个SomethingStored事件。看起来像后端一个set接口但它背后是完整的链上状态变更。注意不同版本的 Substrate 宏语法会有细微差异你写代码时要以当前依赖版本为准。如果复制模板里的代码报错优先检查frame-support和frame-system的版本是否匹配。4.3 把 pallet 挂到 runtimepallet 写完后还必须“插”到 Runtime 里才能生效这一步也经常有人漏掉。首先在runtime/Cargo.toml里添加这个 pallet 的依赖比如pallet-template { path ../pallets/template, default-features false, version 4.0.0-dev }然后在[features]的std列表里加上pallet-template/std否则编译 native 运行时会报错。接着在runtime/src/lib.rs里实现pallet_template::Configimpl pallet_template::Config for Runtime { type RuntimeEvent RuntimeEvent; }最后在construct_runtime!宏中加入模块construct_runtime!( pub enum Runtime { // ... TemplatePallet: pallet_template, } );重新编译之后你的链就多了一个新的原语操作。你可以通过前端模板调用templatePallet.setSomething调用成功后区块里会多一条对应事件。这个过程就是企业级区块链自定义开发最核心的一个循环写业务模块、挂到运行时、编译、测试、升级。5. 实操中踩过的坑和排查方法5.1 编译慢、内存爆这是新手最常遇到的第一道坎。cargo build --release第一次编译用时二三十分钟是常有的事旧机器甚至可能接近一小时。如果编译过程中直接提示内存不足可以先关掉其他大应用再用CARGO_BUILD_JOBS2限制并行度。还有一个小建议把target目录放到 SSD 上。如果项目在机械硬盘上编译等待时间可以翻倍。也可以把target目录用软链接指到独立分区避免和系统盘抢空间。5.2 Runtime 升级后数据兼容Substrate 无分叉升级很爽但如果你改了某个 pallet 的存储结构比如把一个字段从u32改成u64旧数据不会自动迁移。必须先写 storage migration也就是在升级逻辑里处理旧数据如何转换成新结构。我见过有人直接改掉存储类型后升级结果链运行时出现数据读取错乱最后只能重置链。升级前一定要先在本地开发环境做一次完整测试。启动一条测试链写入一些测试数据再对 Runtime 做升级确认数据迁移正常后再考虑放到正式环境。5.3 Wasm 与 Native 执行差异启动节点时日志里经常会看到running in native mode或running in wasm mode的提示。Native 模式执行速度快适合本地调试Wasm 模式是链上标准。两者的执行结果应当一致但如果你用了某些不稳定的依赖或写了未定义行为可能出现 native 跑得好、上链后结果不同的问题。排查这类问题最有效的办法是使用 Substrate 提供的try-runtime工具它可以在本地用链上历史区块数据回放 Runtime 执行帮助发现 Wasm 与 native 不一致的隐患。别嫌额外配置麻烦重点这能省下大量线上事故。5.4 前端连不上节点前端模板默认连接ws://127.0.0.1:9944。如果连不上先确认节点还在运行再确认节点启动日志里 RPC 端口确实是 9944。有些环境需要显式指定./target/release/node-template --dev --rpc-port 9944 --ws-port 9944另外如果你把节点跑在远程服务器上记得配置 WebSocket 的监听地址让它监听0.0.0.0同时在前端配置里填真实服务器 IP。如果用了防火墙也要放行对应端口。6. Substrate 的适用边界和个人心得6.1 什么场景应该用 Substrate如果你有以下需求Substrate 是很稳妥的选择。第一你需要一条“业务链”而不是“通用公链”。比如供应链溯源、积分系统、数字资产存证你可以把业务流程直接写成 pallet而不是在智能合约里绕来绕去。第二你需要较强的可定制性包括自定义共识、自定义手续费、自定义账户权限。Substrate 几乎每一层都能替换。第三你有长期维护的能力愿意用 Rust 开发和维护一套独立代码库。如果你只是想快速发一个币那不用 Substrate智能合约就够。6.2 什么场景我劝你慎重不要因为 Substrate 很强大就盲目上车。如果你的团队完全没有 Rust 开发经验而业务又只需要一个简单的数据库系统那 Substrate 反而会成为负担。它本质上是一套复杂的分布式系统框架学习曲线陡峭编译时间长调试工具也远不如传统后端成熟。如果只是想做一个内部管理系统用 Postgres 加几条业务表效率高得多。另外如果你的业务对最终性要求极高或者你只需要一条“私有链”也要评估清楚。Substrate 可以做成许可链但为了跑一条几个人用的小链引入一整条共识和网络协议运维成本并不低。6.3 一点个人体会用 Substrate 做了几个项目之后我的总体感受是它不是一个“拿来即用”的工具更像一个能力上限很高的扩展框架。初期繁琐中期顺畅后期如果你能把 pallet 和 Runtime 结构设计得足够清晰迭代速度反而比传统后端更快。我的建议是不要一上来就啃源码或文档先跑通 node-template再照葫芦画瓢改一个 pallet最后再回头理解 Client、Runtime、Wasm 之间的关系。区块链开发最大的特点就是“一旦状态上链就很难改”所以前期设计、测试、迁移方案做得越细后期麻烦越少。这套经验适用于任何想用 Substrate 造一条链的人。
返回列表