ARTICLE DETAIL

资讯详情

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

Substrate FRAME dev_mode 模式实战:用最小样板快速开发 Pallet 示例(pallet-dev-mode 解析)

Substrate FRAME dev_mode 模式实战:用最小样板快速开发 Pallet 示例(pallet-dev-mode 解析) 区块链开发框架后端【免费下载链接】substrateSubstrate: The platform for blockchain innovators项目地址https://gitcode.com/gh_mirrors/su/substrate点击查看免费下载导读本文围绕 Substrate 仓库中的官方示例 frame/examples/dev-mode/README.md 及其配套源码深入讲解 FRAME 提供的#[pallet(dev_mode)]开发模式它允许开发者省略权重、调用索引、MaxEncodedLen与存储 hasher 等生产级样板代码以最少代码快速验证 Pallet 逻辑。读完本文你将掌握 dev_mode 的四个核心豁免规则、pallet-dev-mode示例的完整实现与测试方式以及如何把 dev_mode Pallet 平滑迁移到生产模式。需要特别强调的是dev mode 仅用于原型开发与试验绝不应用于生产环境。示例概述pallet-dev-mode是什么Substrate 官方在frame/examples下提供了多个 Pallet 示例dev-mode是其中之一。它的定位非常明确用一段尽可能少的代码展示在 dev mode 下编写一个 FRAME Pallet 有多么简单。在 README.md 中官方这样描述它A simple example of a FRAME pallet demonstrating the ease of requirements for a pallet in dev mode.即一个展示 dev mode 下 Pallet 需求之简便的简单示例。同文件还给出了两个关键提示运行cargo doc --package pallet-dev-mode --open可以查看该 Pallet 的文档Dev mode 不应用于生产环境Dev mode is not meant to be used in production。从 Cargo.toml 可以看到该 crate 名为pallet-dev-mode版本为4.0.0-dev许可证为MIT-0依赖frame-support、frame-system、pallet-balances等并声明了std与try-runtime特性具备标准 FRAME Pallet 的完整工程结构。运行环境与查看文档如果你已在本仓库根目录可以直接用下面的命令构建并打开该示例 Pallet 的 rustdoc 文档cargo doc --package pallet-dev-mode --open--open会在构建完成后自动在浏览器中打开生成的文档页面。该文档源自 src/lib.rs 顶部与各注释的 doc 内容正文中同样标注了Dev mode is not meant to be used in production的警告。运行单元测试cargo test -p pallet-dev-mode测试代码位于 src/tests.rs我们在下文第五节会逐条分析。源码逐段解读一个极简 Pallet 的骨架整个 Pallet 的实现非常紧凑全部代码只有约 90 行位于 src/lib.rs。我们按 FRAME Pallet 的标准组成部分逐一拆解。3.1 开启 dev mode 的关键一行// Enable dev_mode for this pallet. #[frame_support::pallet(dev_mode)] pub mod pallet {这是整个示例的灵魂在#[frame_support::pallet(...)]属性中传入dev_mode参数即可让整个 Pallet 进入开发模式。注意dev_mode只能写在包裹 pallet 模块的最外层属性宏上不能写在#[pallet::pallet]、#[pallet::call]等其他位置。这在 frame/support/src/lib.rs 的官方宏文档中有明确说明。文件开头还有一行标准的no_std声明确保 Pallet 可以编译到 Wasm 运行时#![cfg_attr(not(feature std), no_std)]以及pub use pallet::*;重导出让 pallet 条目可以从 crate 命名空间直接访问。3.2 Config只需要声明 RuntimeEvent#[pallet::config] pub trait Config: pallet_balances::Config frame_system::Config { /// The overarching event type. type RuntimeEvent: FromEventSelf IsTypeSelf as frame_system::Config::RuntimeEvent; }Config继承自pallet_balances::Config与frame_system::Config仅声明一个RuntimeEvent关联类型。这意味着该 Pallet 复用了pallet_balances的余额类型后续set_bar调用中会直接使用T::Balance。3.3 Call两个免样板的可调用函数#[pallet::call] implT: Config PalletT { // No need to define a call_index attribute here because of dev_mode. // No need to define a weight attribute here because of dev_mode. pub fn add_dummy(origin: OriginForT, id: T::AccountId) - DispatchResult { ensure_root(origin)?; if let Some(mut dummies) Dummy::T::get() { dummies.push(id.clone()); Dummy::T::set(Some(dummies)); } else { Dummy::T::set(Some(vec![id.clone()])); } // Lets deposit an event to let the outside world know this happened. Self::deposit_event(Event::AddDummy { account: id }); Ok(()) } // No need to define a call_index attribute here because of dev_mode. // No need to define a weight attribute here because of dev_mode. pub fn set_bar( origin: OriginForT, #[pallet::compact] new_value: T::Balance, ) - DispatchResult { let sender ensure_signed(origin)?; // Put the new value into storage. BarT::insert(sender, new_value); Self::deposit_event(Event::SetBar { account: sender, balance: new_value }); Ok(()) } }两个调用的功能分别为add_dummy(origin, id)要求ensure_root仅根级来源可调用向Dummy存储中追加一个账户 ID并发出AddDummy事件set_bar(origin, new_value)要求ensure_signed已签名来源以调用者账户为键写入Bar存储映射并发出SetBar事件。参数使用了#[pallet::compact]编码优化。注意注释反复强调的两点不需要call_index、不需要weight——这正是 dev_mode 的意义所在我们第四节展开讲。3.4 Event轻量事件枚举#[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum EventT: Config { AddDummy { account: T::AccountId }, SetBar { account: T::AccountId, balance: BalanceOfT }, }通过#[pallet::generate_deposit(pub(super) fn deposit_event)]自动生成deposit_event辅助函数供两个 call 内部调用。事件携带账户与余额信息便于链外索引与监听。3.5 Storagedev_mode 下的宽容存储声明/// The MEL requirement for bounded pallets is skipped by dev_mode. /// This means that all storages are marked as unbounded. /// This is equivalent to specifying #[pallet::unbounded] on this type definitions. /// When the dev_mode is removed, we would need to implement MaxEncodedLen. #[pallet::storage] pub type DummyT: Config StorageValue_, VecT::AccountId; /// The Hasher requirement is skipped by dev_mode. So, second parameter can be _ /// and Blake2_128Concat is used as a default. /// When the dev_mode is removed, we would need to specify the hasher like so: /// pub type BarT: Config StorageMap_, Blake2_128Concat, T::AccountId, T::Balance;. #[pallet::storage] pub type BarT: Config StorageMap_, _, T::AccountId, T::Balance;两个存储分别演示了 dev_mode 的两种豁免Dummy是StorageValue_, VecT::AccountId一个无界的Vec集合。在普通模式下这类存储需要实现MaxEncodedLenMEL或用#[pallet::unbounded]显式声明在 dev_mode 下被自动视为无界等价于对所有存储声明#[pallet::unbounded]Bar是StorageMap_, _, T::AccountId, T::Balance第二个类型参数hasher 位置直接写成_。在普通模式下必须显式指定 hasher如Blake2_128Concatdev_mode 会自动填入Blake2_128Concat作为默认值。类型别名BalanceOfT定义在文件顶部/// A type alias for the balance type from this pallets point of view. type BalanceOfT T as pallet_balances::Config::Balance;dev_mode 的四大豁免规则原理与源码印证frame/support/src/lib.rs 中的#[pallet]宏文档系统阐述了 dev mode 的全部效果与示例代码一一对应4.1 权重Weight豁免默认零权重Weights no longer need to be specified on every#[pallet::call]declaration. By default, dev mode pallets will assume a weight of zero (0) if a weight is not specified.普通模式中每个#[pallet::call]函数必须通过#[pallet::weight($expr)]给出权重或在#[pallet::call(weight ...)]上提供继承权重否则编译失败错误信息见 parse/call.rs。而在 dev mode 下宏会自动注入零权重在 parse/call.rs 中可以看到if weight_attrs.is_empty() dev_mode { // inject a default O(1) weight when dev mode is enabled and no weight has // been specified on the call let empty_weight: syn::Expr syn::parse(quote::quote!(0).into()) .expect(we are parsing a quoted string; qed); weight_attrs.push(FunctionAttr::Weight(empty_weight)); }即当 dev_mode 开启且调用未标注权重时宏注入字面量0等价于对每个调用写上#[pallet::weight(0)]。注释里特别说明这是一个 O(1) 的默认值仅用于开发阶段占位。4.2 调用索引Call Index豁免按声明顺序自动编号Call indices no longer need to be specified on every#[pallet::call]declaration. By default, dev mode pallets will assume a call index based on the order of the call.普通模式下显式指定#[pallet::call_index($n)]可以控制调用在RuntimeCall枚举中的编码索引dev mode 下则完全省略该属性宏会按 call 的声明顺序自动分配索引。相关逻辑同样在 parse/call.rs 中处理explicit_call_index为false时走默认顺序索引。4.3 有界性MEL豁免所有存储视为无界All storages are marked as unbounded, meaning you do not need to implementMaxEncodedLenon storage types. This is equivalent to specifying#[pallet::unbounded]on all storage type definitions.在 parse/storage.rs 中// set all storages to be unbounded if dev_mode is enabled unbounded | dev_mode;unbounded标志位在 dev_mode 下被强制置为true因此Dummy这类Vec存储无需实现MaxEncodedLen。代价是这类存储的编码长度在链上不可预估这正是它不适合生产的原因之一。4.4 存储 hasher 豁免默认Blake2_128ConcatStorage hashers no longer need to be specified and can be replaced by_. In dev mode, these will be replaced byBlake2_128Concat. In case of explicit key-binding,Hashercan simply be ignored when indev_mode.在 parse/storage.rs 中_占位符在 dev_mode 下被合法接受并默认解析为Blake2_128Concat若非 dev_mode 却使用_则会得到编译错误_can only be used in dev_mode. Please specify an appropriate hasher.框架侧还提供了更完整的编译通过用例供对照学习dev_mode_valid.rs 中展示了StorageValue、StorageMap、StorageDoubleMap、CountedStorageMap以及具名键绑定如StorageMapKey u32, Value u64在 dev_mode 下的各种写法——具名形式甚至可以完全不写Hasher字段。4.5 官方警告frame/support/src/lib.rs 用醒目的方式给出警告WARNING: You should not deploy or use dev mode pallets in production. Doing so can break your chain and therefore should never be done. Once you are done tinkering, you should remove the dev_mode argument from your #[pallet] declaration and fix any compile errors before attempting to use your pallet in a production scenario.即零权重会让交易几乎不消耗费用无界存储会破坏存储计费与 PoV 大小预估这些在生产环境中都可能破坏链的正常运行。测试验证用 mock runtime 验证两个 Callsrc/tests.rs 展示了 dev_mode Pallet 的完整测试套路构造 mock 运行时 → 构建测试外部环境 → 执行并断言。5.1 Mock 运行时构造测试使用frame_support::construct_runtime!组装了System、Balances与Example三个模块frame_support::construct_runtime!( pub enum Test { System: frame_system::{Pallet, Call, ConfigT, Storage, EventT}, Balances: pallet_balances::{Pallet, Call, Storage, ConfigT, EventT}, Example: pallet_dev_mode::{Pallet, Call, Storage, EventT}, } );其中Example即本示例 Pallet通过use crate as pallet_dev_mode;以 pallet 名重新导出。frame_system::Config使用ConstU64250作为BlockHashCount、ConstU3216作为MaxConsumerspallet_balances::Config使用ConstU641作为最小存在存款账户类型为u64。测试环境构建函数pub fn new_test_ext() - sp_io::TestExternalities { let t RuntimeGenesisConfig { // We use default for brevity, but you can configure as desired if needed. system: Default::default(), balances: Default::default(), } .build_storage() .unwrap(); t.into() }5.2 测试一Dummy的累积追加it_works_for_optional_value验证add_dummy的首次初始化 后续追加行为#[test] fn it_works_for_optional_value() { new_test_ext().execute_with(|| { assert_eq!(Dummy::Test::get(), None); let val1 42; assert_ok!(Example::add_dummy(RuntimeOrigin::root(), val1)); assert_eq!(Dummy::Test::get(), Some(vec![val1])); // Check that accumulate works when we have Some value in Dummy already. let val2 27; assert_ok!(Example::add_dummy(RuntimeOrigin::root(), val2)); assert_eq!(Dummy::Test::get(), Some(vec![val1, val2])); }); }断言逻辑对应add_dummy中的if let Some(mut dummies)分支存储为空时创建vec![val1]已有值时 push 追加。调用使用RuntimeOrigin::root()匹配ensure_root的权限要求。5.3 测试二Bar的写入与读取set_dummy_works验证set_bar的签名来源与存储写入#[test] fn set_dummy_works() { new_test_ext().execute_with(|| { let test_val 133; assert_ok!(Example::set_bar(RuntimeOrigin::signed(1), test_val.into())); assert_eq!(Bar::Test::get(1), Some(test_val)); }); }使用RuntimeOrigin::signed(1)构造签名来源写入后通过Bar::Test::get(1)读取验证存储映射以账户为键正确落盘。从 dev_mode 平滑迁移到生产模式示例源码的注释已经给出了迁移路径。当原型验证完成、准备生产时需要按顺序处理以下事项移除dev_mode参数将#[frame_support::pallet(dev_mode)]改回#[frame_support::pallet]补齐权重为每个#[pallet::call]添加#[pallet::weight($expr)]或使用#[pallet::call(weight T as crate::Config::WeightInfo)]继承权重语法见 parse/mod.rs并配套基准测试生成真实的WeightInfo补齐调用索引如需要稳定编码为 call 添加#[pallet::call_index($n)]处理存储有界性为Dummy这类Vec存储实现MaxEncodedLen或显式标注#[pallet::unbounded]显式指定 hasher将Bar的第二个参数_改为Blake2_128Concat或其他合适的 hasher如源码注释给出的写法pub type BarT: Config StorageMap_, Blake2_128Concat, T::AccountId, T::Balance;完成上述调整后重新编译并修复所有由严格检查暴露出的编译错误即可进入生产评审流程。总结与适用边界pallet-dev-mode是学习 FRAME Pallet 开发的绝佳入门示例它用约 90 行代码覆盖了 Config、Call、Event、Storage 的全部组成部分并通过dev_mode豁免了生产 Pallet 中最繁琐的四类样板权重、调用索引、MEL、hasher让开发者把注意力集中在业务逻辑本身。框架侧的实现parse/call.rs、parse/storage.rs与官方文档frame/support/src/lib.rs共同定义了 dev_mode 的精确语义配套的 UI 通过用例dev_mode_valid.rs则进一步覆盖了更多存储形态。适用边界务必牢记dev_mode 是开发加速器不是生产捷径。零权重、无界存储等宽容设定仅适合本地试验、单元测试与原型验证任何打算上线运行的链都必须移除dev_mode并补齐全部生产级要求。建议的开发流程是先用 dev_mode 快速验证逻辑再按上文第六节的清单完成迁移最后以严格的编译与基准测试收尾。赞分享区块链开发框架后端【免费下载链接】substrateSubstrate: The platform for blockchain innovators项目地址https://gitcode.com/gh_mirrors/su/substrate点击查看免费下载相关推荐WebLLM 如何生成 SRI 哈希并为模型配置完整性校验IntegrityWebLLM 如何生成 SRI 哈希并为模型配置完整性校验Integrity 当你自己托管模型产物 mlc chat config.json 、WASM区块链开发框架后端Loop2个按键搞定Mac窗口管理4个高频场景直接会Loop2个按键搞定Mac窗口管理4个高频场景直接会 你是否遇到过这样的时刻鼠标拖着窗口角落想让它和屏幕边缘对齐却总是差那么几像素多个窗口堆在桌面上区块链开发框架后端Substrate Tips Palletpallet-tips深入解析基于 Treasury 的敏捷打赏机制与源码实现Substrate Tips Palletpallet tips深入解析基于 Treasury 的敏捷打赏机制与源码实现 导读 本文系统讲解 Substr区块链开发框架后端上一篇CUA-Bench Basic 数据集实战指南用 13 个基础 UI 交互任务评测计算机使用智能体下一篇深入解析 lo 库的 ElementsMatchGo 泛型下忽略顺序的切片元素匹配创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表