ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

【Substrate Collectables教程】【第2章Kitties】3 追踪所有 Kitties

2022-05-13 21:31:46  阅读:238  来源: 互联网

标签:count Substrate people 列表 let new Collectables runtime Kitties


追踪所有 Kitties

现在我们已经让每个用户都可以创建自己独一无二的 kitty,我们开始追踪它们!

我们的游戏将会追踪创建的 kitty 总数,以及追踪谁拥有哪只 kitty。

 

作为基于 Substrate 框架的应用开发人员,很重要的一点是要区分 Substrate 上 runtime 的逻辑设计和 Ethereum 平台上的智能合约开发的不同。

在 Ethereum 中,如果你的交易在任何时候失败(错误,没有 gas 等...),你的智能合约的状态将不受影响。但是,在 Substrate 上并非如此。一旦交易开始修改区块链的存储,这些更改就是永久性的,即使交易在 runtime 执行期间失败也是如此。

这对于区块链系统是必要的,因为你可能想要追踪用户的 nonce 或者为任何发生的计算减去 gas 费用。对于失败的交易来说,这两件事实际上都发生在 Ethereum 状态转换函数中,但你作为智能合约开发人员,从来不必担心去管理这些事情。

既然现在你是 Substrate runtime 开发人员,你必须察觉到你对区块链状态所做的任何更改,并确保它遵循 "verify first, write last" 的模式。我们将在整个教程中帮助你做到这点。

 

3.1 创建一个 List

在 runtime 开发中,列表循环通常是坏事。如果没有明确对其防范,枚举一个列表的 runtime 函数会增加 O(N) 的复杂度,但是仅仅花费了 O(1) 的费用。结果就是你的链变得容易被攻击。并且,如果你所枚举的列表过大甚至是无限的,你的 runtime 可能需要比区块生成的间隔更多的时间。这意味着区块生产者不能正常地生产区块!

基于上述原因,本教程在 runtime 逻辑中不会使用任何列表循环。如果你选择使用,请确保已经考虑清楚。

作为替代,我们可以使用映射和计数器模拟列表,如下所示:

decl_storage! {
    trait Store for Module<T: Trait> as Example {
        AllPeopleArray get(person): map u32 => T::AccountId;
        AllPeopleCount get(num_of_people): u32;
    }
}

这里我们将在 runtime 中存储人员列表,用多个 AccountId 表示。我们只需要小心谨慎地维护这些存储项目,以确保它们准确和最新。

 

3.2 检查 Overflow/Underflow

如果你曾经在 Ethereum 上开发过,那么如果你不执行 “safe math”,你就会碰到你所熟悉的问题,即 Overflow/Underflow。Overflow 和 Underflow 很容易就可以使 runtime 出现 panic 或者存储混乱。

在更改存储状态之前,你必须始终主动检查可能的 runtime 错误。请记住,与 Ethereum 不同,当交易失败时,状态不会恢复到交易发生之前,因此你有责任确保在错误处理上不会产生任何副作用。

幸运的是,在 Rust 中检查这些类型的错误非常简单,其中原始数字类型具有 checked_add() 和 checked_sub() 函数。

假设我们想要向 AllPeopleArray 中添加一项,我们首先要检查我们是否可以成功增加 AllPeopleCount,如下所示:

let all_people_count = Self::num_of_people();

let new_all_people_count = all_people_count.checked_add(1).ok_or("Overflow adding a new person")?;

使用 ok_or 与下面的代码相同:

let new_all_people_count = match all_people_count.checked_add(1) {
    Some (c) => c,
    None => return Err("Overflow adding a new person"),
};

但是,ok_or 比 match 更清晰可读; 你只需要确保记住在末尾加 ?

如果我们成功地能够在没有上溢的情况下递增 AllPeopleCount,那么它就会将新值分配给 new_all_people_count。如果失败,我们的 module 将返回一个 Err(),它可以由我们的 runtime 优雅地处理。错误消息也将直接显示在节点的控制台输出中。

 

3.3 更新存储中的 List

现在我们已经检查过了,我们可以安全地增加列表项,我们最终可以将更改推送到存储中。请记住,当你更新列表时,列表的 “最后一个索引” 比计数少一个。例如,在包含 2 个项的列表中,第一个项是索引 0,第二个项是索引 1。

将新的人员添加到我们的人员列表中,完整示例如下所示:

fn add_person(origin, new_person: T::AccountId) -> Result {
    let sender = ensure_signed(origin)?;

    let all_people_count = Self::num_of_friends();

    let new_all_people_count = all_people_count.checked_add(1).ok_or("Overflow adding a new person")?;

    <AllPeopleArray<T>>::insert(all_people_count, new_people);
    <AllPeopleCount<T>>::put(new_all_people_count);

    Ok(())
}

我们也应该为这个函数添加碰撞检测!你还记得怎么做吗?

 

3.4 删除 List 元素

当我们尝试从列表中间删除元素时,映射和计数模式引入的一个问题就是会在列表中留下空位。幸运的是,在本教程中我们管理的列表的顺序并不重要,因此我们可以使用 "swap and pop" 的方法来有效地缓解此问题。

"swap and pop" 方法交换删除项的位置以及列表中的最后一项。然后,我们可以简单地删除最后一项而不会在我们的列表中引入任何空位。

我们不会在每次删除时运行循环来查找删除项的索引,而是使用一些额外的存储来追踪列表中每个项及其所在的位置。

我们现在不会引入 "swap and pop" 的逻辑,但是我们会要求你使用一个 Index 存储项来追踪列表中每项的索引,如下所示:

AllPeopleIndex: map T::AccountId => u32;

这实际上只是 AllPeopleArray 中内容的反转。请注意,我们这里不需要 getter 函数,因为此存储项只在内部使用,并且不需要作为模块 API 的一部分公开。

 

3.5 示例

use support::{decl_storage, decl_module, StorageValue, StorageMap,
    dispatch::Result, ensure, decl_event};
use system::ensure_signed;
use runtime_primitives::traits::{As, Hash};
use parity_codec::{Encode, Decode};

#[derive(Encode, Decode, Default, Clone, PartialEq)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct Kitty<Hash, Balance> {
    id: Hash,
    dna: Hash,
    price: Balance,
    gen: u64,
}

pub trait Trait: balances::Trait {
    type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
}

decl_event!(
    pub enum Event<T>
    where
        <T as system::Trait>::AccountId,
        <T as system::Trait>::Hash
    {
        Created(AccountId, Hash),
    }
);

decl_storage! {
    trait Store for Module<T: Trait> as KittyStorage {
        Kitties get(kitty): map T::Hash => Kitty<T::Hash, T::Balance>;
        KittyOwner get(owner_of): map T::Hash => Option<T::AccountId>;

        AllKittiesArray get(kitty_by_index): map u64 => T::Hash;
        AllKittiesCount get(all_kitties_count): u64;
        AllKittiesIndex: map T::Hash => u64;

        OwnedKitty get(kitty_of_owner): map T::AccountId => T::Hash;

        Nonce: u64;
    }
}

decl_module! {
    pub struct Module<T: Trait> for enum Call where origin: T::Origin {

        fn deposit_event<T>() = default;

        fn create_kitty(origin) -> Result {
            let sender = ensure_signed(origin)?;

            let all_kitties_count = Self::all_kitties_count();

            let new_all_kitties_count = all_kitties_count.checked_add(1)
            .ok_or("Overflow adding a new kitty to total supply")?;

            let nonce = <Nonce<T>>::get();
            let random_hash = (<system::Module<T>>::random_seed(), &sender, nonce)
                .using_encoded(<T as system::Trait>::Hashing::hash);

            ensure!(!<KittyOwner<T>>::exists(random_hash), "Kitty already exists");

            let new_kitty = Kitty {
                id: random_hash,
                dna: random_hash,
                price: <T::Balance as As<u64>>::sa(0),
                gen: 0,
            };

            <Kitties<T>>::insert(random_hash, new_kitty);
            <KittyOwner<T>>::insert(random_hash, &sender);

            <AllKittiesArray<T>>::insert(all_kitties_count, random_hash);
            <AllKittiesCount<T>>::put(new_all_kitties_count);
            <AllKittiesIndex<T>>::insert(random_hash, all_kitties_count);

            <OwnedKitty<T>>::insert(&sender, random_hash);

            <Nonce<T>>::mutate(|n| *n += 1);

            Self::deposit_event(RawEvent::Created(sender, random_hash));

            Ok(())
        }
    }
}

 

标签:count,Substrate,people,列表,let,new,Collectables,runtime,Kitties
来源: https://www.cnblogs.com/xiangshigang/p/16268274.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有