ARTICLE DETAIL

资讯详情

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

Substrate区块链开发框架:从Runtime到Pallets的实战解析

Substrate区块链开发框架:从Runtime到Pallets的实战解析 做区块链底层开发这几年我越来越觉得“框架”和“轮子”是两码事。轮子是现成的你拿来装上车就能跑框架是一套完整的生产线逻辑它给了你造车所有的工位和标准但你得自己决定造轿车还是卡车。Substrate就是后者。这个原本在材料学里叫“基底”的词被Parity团队拿来命名一个区块链开发框架之后几乎成了“可定制区块链”的代名词。很多朋友一听说Substrate就跟Polkadot画等号这不完全对。Polkadot只是用Substrate造出来的一条链它远不是全部。这篇文章我想抛开官方的那些宣传口径从一个实际写过、跑过、被坑过的开发者角度聊聊Substrate到底能做什么、它设计上最聪明的地方在哪、以及你从零开始搭一条链的时候最可能踩到哪些雷。如果有朋友正打算入坑区块链底层开发或者说想给自己的项目选型一个底层框架这篇应该能给你省下不少走弯路的时间。1. 开发者的第一问Substrate到底解决了传统链开发的什么痛点先聊点背景。早些年想搞一条自己的链基本就两条路一条是拉一套Bitcoin或者Ethereum的代码来改另一条是用现成的联盟链平台搭个链。不管哪条路你都会撞上几堵墙。第一堵墙是共识层和状态层的强耦合。以太坊的代码里共识逻辑、交易执行、账户状态是死死捆在一起的。你想换个共识算法牵一发动全身改完还不知道哪里会崩。第二堵墙是升级难题。链一旦跑起来想要改业务逻辑就麻烦了硬分叉是唯一出路搞过的人都知道那是多大的工程和社区折腾。第三堵墙是开发门槛。链上跑的业务逻辑和底层网络、数据库、共识搅在一起新人想上手光把代码结构吃透就得一两个月。Substrate在设计上等于把这几堵墙直接拆了。它把区块链分成几个清晰的大块外围网络层、共识层、存储层以及最核心的Runtime。开发者绝大部分精力只需要花在Runtime上其他东西框架已经给你提供了一套默认但是高性能的实现。你想换哪个部件直接在对应位置插入你的实现就行不需要推翻重来。有人可能会问这和“模块化区块链”是不是一个意思方向上有些接近但Substrate更底层的贡献在于它把一个区块链节点应该具备的所有能力做成了标准化组件而你写业务逻辑用的是一套独立的领域模型。这套模型让你写的代码和底层实现之间有一道明确的边界边界带来的好处就是可替换、可测试、可升级。当年我选型的时候也对比过Cosmos SDK。Cosmos SDK的思路是给你一套Tendermint共识加ABCI接口业务逻辑通过接口跟共识交互。Substrate走的则是一条更激进的路连共识都可以由你定义系统不仅分区块还有一套完整的状态转换函数抽象。两者都能做出链来但Substrate在多链架构、跨链通信和无分叉升级这些方向上明显走得更早这也是后来Polkadot生态能快速长出上百条平行链的重要前提。2. 核心设计拆解Runtime、FRAME与Pallets到底各司其职什么如果你去看Substrate的源码一开始大概率是懵的因为东西太多了。但你不必全部理解先抓住三条主线就行Runtime、FRAME、Pallets。2.1 Runtime链的灵魂所在Runtime的中文翻译通常叫“运行时”它的本质就是描述区块链状态如何变化的逻辑集合。从账户余额转到交易手续费从投票结果到某个DApp的自定义状态全部都在Runtime里定义。Substrate的奇妙之处在于它把Runtime编译成了一段可以被验证的二进制而且这段二进制还可以作为链上状态的一部分被存储和更新。这个设计的直接后果就是升级Runtime不需要硬分叉。大家在链上通过一个特殊的交易提交一份新Runtime的二进制然后通过治理机制让它生效。生效之后全网的节点自动执行新的状态转换逻辑就像手机系统在线升级一样共识不会断链也不会分叉。这一点对整个区块链开发领域来说都是颠覆性的。2.2 FRAME写业务逻辑的一整套脚手架FRAME是Substrate为开发者准备的开发框架集合它包含了一系列标准库和宏。你写业务逻辑时更多是在跟FRAME打交道而不是直接面对Runtime的最底层。FRAME里那些宏让你可以用一种声明式的方式定义一条链的结构。比如说你要给一条链加一种数字资产在传统的以太坊上你得写一套ERC-20智能合约在Substrate里你可以直接用balances这个pallet也可以参考assets资产pallet做配置、扩展、组合。写起来的感觉更像是用积木搭系统而不是从零焊铁皮。FRAME还帮你处理了很细节的工作。以pallet的存储为例你只需要用#[pallet::storage]标记一个存储项FRAME就会自动为你生成对应的存储读写接口、事件、错误信息以及测试辅助函数。这意味着代码量和出错概率都能下降一个量级。2.3 Pallets真正的积木单元Pallets可以理解为一组相关的业务功能模块。类似“模块化”这个词在不同框架里的含义不一样在Substrate的语境里Pallet是包含存储、事件、错误、可调用函数、链上共识逻辑校验这些要素的完整单元。FRAME自带了大量官方Pallets比如pallet_balances处理账户余额和转账pallet_stakingPOS质押和验证者奖励pallet_session验证人会话管理pallet_democracy、pallet_collective、pallet_treasury链上治理模块pallet_sudo超级权限模块开发期很常用你可以直接把这些模块组装进自己的Runtime也可以继承和改造它们。如果你的需求官方库里没有完全可以自己写一个。Substrate对自定义Pallet的支持做得非常到位这也是为什么它能承载各种千奇百怪的链上业务包括游戏、DeFi、身份、存储市场。3. 实操从模板拉一条最简链编译与启动的全过程光看概念不过瘾接下来我带你实际跑一条最简链。我以当前主流的Substrate版本对应Polkadot v1.x系列的节点模板为例把从拉代码到链跑起来的全过程过一遍。3.1 环境准备别小看Rust工具链的坑Substrate是Rust写的所以Rust环境是第一关。最省事的方式是安装rustup然后指定nightly工具链。注意一定要按官方指导安装wasm编译目标因为Runtime需要编译成Wasm格式。curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh rustup update nightly rustup target add wasm32-unknown-unknown --toolchain nightly这里有几个容易踩的坑一是rustup默认工具链也许不是nightly你编译Substrate项目时最好在项目根目录放一个rust-toolchain.toml文件指定工具链版本避免系统升级后环境突然不可用。二是编译时间极长。第一次编译节点模板完整构建可能要20到40分钟取决于机器性能。这不是项目卡住了是Rust的依赖确实很多。建议编译时把CARGO_BUILD_JOBS设到合理值内存小的机器别贪多不然容易编译到一半内存耗尽。三是macOS和Linux的差异。macOS上偶尔会有链接器报错多半是缺少一些系统库比如llvm。Linux上则要注意CMake、clang这些基础依赖Ubuntu系一条命令就能装齐sudo apt install build-essential git clang curl libssl-dev llvm libudev-dev3.2 拉取项目模板并生成链官方提供了节点模板仓库substrate-node-template这是最简化的启动项目。直接利用substrate的子命令或者用git模板方式都可以。我用的是最直接的git clone路径git clone https://github.com/substrate-developer-hub/substrate-node-template.git cd substrate-node-template cargo build --release编译完成之后target/release/node-template就是你的全功能节点可执行文件。第一次跑之前先清除旧的链数据因为开发过程中你可能会产生一些脏数据。然后加上--dev参数启动开发模式它可以让你在本地快速独立跑一条链不需要跟任何人连接。./target/release/node-template --dev如果你看到控制台打印出类似“Substrate Node”的启动日志和高度持续增长的区块信息恭喜你的第一条链已经在线跑起来了。3.3 用前端模板连上你的链光有节点还不够需要有个界面去交互。Substrate官方提供了前端模板substrate-front-end-template这是一套React工程通过websocket和你本地节点通信。git clone https://github.com/substrate-developer-hub/substrate-front-end-template.git cd substrate-front-end-template yarn install yarn start浏览器打开localhost:8000你会看到一个类似区块浏览器加交互面板的界面。在这里可以创建账户、转账、查询余额、调用Runtime里的pallet方法。第一次看到自己的链在区块浏览器里稳步出块那种满足感还是相当强烈的。这里要给新手提个醒开发模式--dev是每次启动都会重置状态的所以你在前端创建的那些账户、交易记录节点一重启就会消失。想要持久化数据得跑非dev模式并指定一个--chain参数和数据库目录。4. 无分叉升级为什么这个机制值得单独拿出来说在传统软件开发里升级是个常见话题但在区块链里升级是一个极其敏感的动作。你面对的是分布在全球成千上万个节点它们可能是矿工、验证者、轻客户端一旦协议不兼容就会产生分裂。整个行业过去几年里有过无数次因为协议变更导致的大大小小分叉每一次都是一场混乱。Substrate让升级变成了一种链上行为而不是链下协商的结果。具体流程是这样的Runtime被编译成Wasm二进制之后它本身就是链上状态的一部分。当有人提交一个特殊的Runtime升级交易经过链上治理机制同意之后新的Wasm就会取代旧版本。后续所有的区块都在新逻辑下运行。如果你用了pallet_sudo开发期甚至可以直接用sudo权限一键升级不需要复杂的治理流程测试非常方便。我一开始觉得这也太简单了会不会有安全性问题。后来深入研究才发现设计上它其实做了很多保障所有的Runtime升级操作都有严格的权限和治理控制升级前后的Runtime都可以被执行网络会通过共识机制在某个区块高度自动切换开发者可以通过try-runtime工具在本地模拟升级后的状态提前发现存储迁移的问题我实际试过一次从旧版升级到新的业务逻辑版本。整个过程大概就是写代码、生成Wasm、调用升级交易节点没有任何重启链上数据完整保留后续区块继续跑。跟传统的硬分叉那种如临大敌的感觉对比这个体验真是天壤之别。不过还是要记住一个原则虽然技术上可以随时升级但业务上线前你仍然需要充分测试。升级成功不等于升级后逻辑正确。尤其当你要做存储迁移的时候流程会变得更复杂需要认真处理历史数据的兼容。这块最稳妥的做法是先在本地用try-runtime做一次全链模拟再上测试网最后才考虑主网。5. 运行一条链之后测试、调试和部署中的常见难题跑通最简链只是开始真正考验人的是后面持续开发和运维的阶段。5.1 单元测试与Runtime仿真测试写Rust的开发者通常对单元测试比较熟悉Substrate里写Pallet测试也非常方便。FRAME提供了一个mock机制你可以构造一个最小化的测试Runtime环境然后验证业务逻辑。以最简单的示例如果你给链加了一个自定义Pallet测试文件大致长这样#[cfg(test)] mod tests { use super::*; use frame_support::{assert_ok, parameter_types}; use sp_runtime::BuildStorage; use crate as pallet_template; // 构造测试用的Runtime frame_support::construct_runtime!( pub enum Test { TemplateModule: pallet_template, System: frame_system, } ); parameter_types! { pub const BlockHashCount: u64 250; } impl frame_system::Config for Test { type BaseCallFilter frame_support::traits::Everything; type BlockWeights (); type BlockLength (); type DbWeight (); type RuntimeOrigin RuntimeOrigin; type RuntimeCall RuntimeCall; type RuntimeEvent RuntimeEvent; type Hash sp_core::H256; type Hashing sp_runtime::traits::BlakeTwo256; type AccountId u64; type Lookup sp_runtime::traits::IdentityLookup; type Header sp_runtime::generic::HeaderBlockNumber, sp_runtime::traits::BlakeTwo256; type RuntimeVersion RuntimeVersion; type PalletInfo PalletInfo; type OnNewAccount (); type OnKilledAccount (); type SS58Prefix (); type SystemWeightInfo (); type Block Block; type BlockNumber u64; type Nonce u64; type Version (); } impl pallet_template::Config for Test { type RuntimeEvent RuntimeEvent; type WeightInfo (); } #[test] fn test_something() { new_test_ext().execute_with(|| { assert_ok!(TemplateModule::do_something(RuntimeOrigin::signed(1), 42)); assert_eq!(TemplateModule::something(), Some(42)); }); } }这些测试跑起来很快不需要起节点是迭代开发的主阵地。我个人的习惯是每写一个Pallet的可调用函数就至少补一个成功用例和一个失败用例后面回归测试会轻松不少。5.2 日志、日志还是日志在链上调试问题的时候最痛苦的就是黑盒运行。Substrate提供了一套不错的日志体系log和runtime的打印信息都能输出到控制台。你可以在Pallet里加入临时日志比如打印某个关键状态变更的前后值帮助定位问题。这些日志建议封装成带runtime目标的记录这样查看的时候直接过滤。如果遇到极端情况比如某个区块执行到一半没法继续我一般会在本地用--dev模式重现问题再把相关Pallet的日志级别调到最高一步步缩小范围。多数情况下都是存储key构造错误或者权重设置不合理导致的。5.3 上线部署时最容易忽视的几件事从开发环境走向生产环境很多人会在几个地方栽跟头。一是节点认证方式。本地开发节点默认允许任何人通过RPC连接生产环境需要关掉不必要的RPC方法并用--rpc-port限制访问甚至在前面加一层反向代理做认证。二是网络参数。部署时必须明确指定chain spec种子节点地址以及bootnode列表。你不能指望一个全新的节点能自动找到整个网络必须给它一个初始的“联系方式”。三是数据库选择。Substrate默认是RocksDB大多数场景够用。如果数据量大、读写频繁可以考虑ParityDB。但注意切换存储引擎前一定要先备份快照否则数据格式不兼容会让你怀疑人生。四是监控。上线前把Prometheus监控开起来区块高度、出块间隔、peer数量这些指标都要盯。很多时候出问题之前这些指标会先出现异常。Substrate节点自带的--prometheus-port参数就能开启非常方便。6. 从Substrate生态里学到的选型思路和它未来还能怎么玩如果你已经跟着上面的内容跑通了一条最简链估计你也能感受到Substrate带给开发者的不是某个单一功能而是一整套思路把区块链的“确定性”和“可组合性”同时做到极致。6.1 不是在写链而是在写多方协作的规则写传统应用你的代码只管一个人的行为写基于Substrate的链你的代码管的是一个群体如何在无信任环境里形成共识。这个思维转变比学习Rust本身更重要。比如一个简单转账函数你要考虑的是签名验证、余额检查、手续费扣除、事件记录、权重计算这些都由框架帮你兜底但你得理解它们的意义才知道怎么正确配置自己的业务逻辑。6.2 基于Substrate还能做什么从公链到联盟链再到L1的各种姿势很多人误以为Substrate只能用来做Polkadot那样的公链。其实它的用途广得多做一条具备自定义治理规则的公链比如链上国库、投票、提案系统做一条联盟链或企业链不需要PoW或大范围POS直接用权威节点出块配合严格的权限控制做应用链把某个垂直场景的所有业务逻辑都搬到链上利用Substrate的通用性做到业务状态可验证作为基础设施去孵化多条业务链再用跨链消息协议把链和链连通形成一个生态我还见过有人用Substrate搭去中心化存储市场、身份聚合系统、NFT交易平台甚至链上游戏。这些项目不全是Polkadot生态的平行链很多就是独立运行的自定义链。Substrate的开放性让这些想象力有了落地的地基。6.3 适合自己的组合比盲目追新重要每次看到新版本发布、新Pallet出现都会有开发者急着往项目里塞。我的建议是审视一下你的场景你真的需要复杂的治理模块吗你的业务真的需要跨链功能吗如果不是那就砍掉。Substrate的理念虽然是模块化但模块化不是让你全都要装上而是让你只选合适的。我用Substrate的这些日子最大的感受就是它不像是一个单一的软件更像是一套工具链的标准。你可以用最简单的方式起步但它的天花板又很高高到可以容纳不同的共识、不同的业务模型、不同的治理哲学。最后分享一条我个人非常推荐的学习路线先跑通节点模板再尝试改一个现有Pallet逻辑然后自己写一个最简单的自定义Pallet最后如果有兴趣再去研读Polkadot原代码里那些复杂的调度和跨链通信实现。走完这条路你对区块链底层的理解会比单纯看白皮书深刻得多。
返回列表