Generate Unique DNA
In this step, we will show how to generate uniqueness using information from the blockchain.
Randomness
Ideally, we would give every kitty we mint a randomly generated DNA.
However generating randomness on a blockchain is extremely difficult because we must come to consensus over all logic and data used on the blockchain. Any kind of randomness function must generate exactly the same randomness for all nodes. And if that is the case, it is still possible to influence randomness as a block producer by choosing NOT to build a block with a randomness you do not like.
Polkadot does provide access to a verifiable random function (VRF), but exactly the properties of this VRF and how to use it is beyond the scope of this tutorial. Not to mention we are also iteratively improving the VRF provided by Polkadot.
Uniqueness
So rather than using true randomness, we will instead try to generate uniqueness.
There are different levels of uniqueness we can achieve using data from our blockchain.
frame_system::Pallet::<T>::parent_hash()
: The hash of the previous block. This will ensure uniqueness for every fork of the blockchain.frame_system::Pallet::<T>::block_number()
: The number of the current block. This will obviously be unique for each block.frame_system::Pallet::<T>::extrinsic_index()
: The number of the extrinsic in the block that is being executed. This will be unique for each extrinsic in a block.CountForKitties::<T>::get()
: The number of kitties in our blockchain.
If we combine all of these things together, we can ensure that every kitty we mint will be unique, no matter:
- Which block it comes in.
- How many extrinsics are in a block.
- Or even if a single extrinsic mints multiple kitties.
Hash
Obviously our uniqueness inputs are not super useful as is. But we can convert these inputs into a unique set of bytes with fixed length using a Hash function like frame::primitives::BlakeTwo256
.
#![allow(unused)] fn main() { // Collect our unique inputs into a single object. let unique_payload = (item1, item2, item3); // To use the `hash_of` API, we need to bring the `Hash` trait into scope. use frame::traits::Hash; // Hash that object to get a unique identifier. let hash: [u8; 32] = BlakeTwo256::hash_of(&unique_payload).into(); }
The hash_of
API comes from the Hash
trait and takes any encode
-able object, and returns a H256
, which is a 256-bit hash. As you can see in the code above, it is easy to convert that to a [u8; 32]
by just calling .into()
, since these two types are equivalent.
Another nice thing about using a hash is you get some sense of pseudo-randomness between the input and output. This means that two kitties which are minted right after one another could have totally different DNA, which could be useful if you want to associate unique attributes to the different parts of their DNA. 🤔
Your Turn
Now that you know how to acquire uniqueness from your blockchain, and how to hash those items, create a new function called fn gen_dna() -> [u8; 32];
which does these steps to create unique DNA for each kitty that is minted.
Update your create_kitty
extrinsic to generate and use this unique DNA.
#![allow(unused)] fn main() { use super::*; use frame::prelude::*; /* 🚧 TODO 🚧: Import `frame::primitives::BlakeTwo256`. */ /* 🚧 TODO 🚧: Import `frame::traits::Hash`. */ impl<T: Config> Pallet<T> { /* 🚧 TODO 🚧: Create a function `gen_dna` which returns a `[u8; 32]`. - Create a `unique_payload` which contains data from `frame_system::Pallet::<T>`: - `parent_hash` - `block_number` - `extrinsic_index` - `CountForKitties::<T>::get()` - Use `BlakeTwo256` to calculate the `hash_of` the unique payload. - Return the hash as a `[u8; 32]`. */ pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { let kitty = Kitty { dna, owner: owner.clone() }; // Check if the kitty does not already exist in our storage map ensure!(!Kitties::<T>::contains_key(dna), Error::<T>::DuplicateKitty); let current_count: u32 = CountForKitties::<T>::get(); let new_count = current_count.checked_add(1).ok_or(Error::<T>::TooManyKitties)?; Kitties::<T>::insert(dna, kitty); CountForKitties::<T>::set(new_count); Self::deposit_event(Event::<T>::Created { owner }); Ok(()) } } }
#![allow(unused)] #![cfg_attr(not(feature = "std"), no_std)] fn main() { mod impls; mod tests; use frame::prelude::*; pub use pallet::*; #[frame::pallet(dev_mode)] pub mod pallet { use super::*; #[pallet::pallet] pub struct Pallet<T>(core::marker::PhantomData<T>); #[pallet::config] pub trait Config: frame_system::Config { type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; } #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(T))] pub struct Kitty<T: Config> { // Using 32 bytes to represent a kitty DNA pub dna: [u8; 32], pub owner: T::AccountId, } #[pallet::storage] pub(super) type CountForKitties<T: Config> = StorageValue<Value = u32, QueryKind = ValueQuery>; #[pallet::storage] pub(super) type Kitties<T: Config> = StorageMap<Key = [u8; 32], Value = Kitty<T>>; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event<T: Config> { Created { owner: T::AccountId }, } #[pallet::error] pub enum Error<T> { TooManyKitties, DuplicateKitty, } #[pallet::call] impl<T: Config> Pallet<T> { pub fn create_kitty(origin: OriginFor<T>) -> DispatchResult { let who = ensure_signed(origin)?; /* 🚧 TODO 🚧: Use the `Self::gen_dna()` function to generate a unique Kitty. */ let dna = [0u8; 32]; Self::mint(who, dna)?; Ok(()) } } } }
#![allow(unused)] fn main() { use super::*; use frame::prelude::*; use frame::primitives::BlakeTwo256; use frame::traits::Hash; impl<T: Config> Pallet<T> { // Generates and returns DNA pub fn gen_dna() -> [u8; 32] { // Create randomness payload. Multiple kitties can be generated in the same block, // retaining uniqueness. let unique_payload = ( frame_system::Pallet::<T>::parent_hash(), frame_system::Pallet::<T>::block_number(), frame_system::Pallet::<T>::extrinsic_index(), CountForKitties::<T>::get(), ); BlakeTwo256::hash_of(&unique_payload).into() } pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { let kitty = Kitty { dna, owner: owner.clone() }; // Check if the kitty does not already exist in our storage map ensure!(!Kitties::<T>::contains_key(dna), Error::<T>::DuplicateKitty); let current_count: u32 = CountForKitties::<T>::get(); let new_count = current_count.checked_add(1).ok_or(Error::<T>::TooManyKitties)?; Kitties::<T>::insert(dna, kitty); CountForKitties::<T>::set(new_count); Self::deposit_event(Event::<T>::Created { owner }); Ok(()) } } }
#![allow(unused)] #![cfg_attr(not(feature = "std"), no_std)] fn main() { mod impls; mod tests; use frame::prelude::*; pub use pallet::*; #[frame::pallet(dev_mode)] pub mod pallet { use super::*; #[pallet::pallet] pub struct Pallet<T>(core::marker::PhantomData<T>); #[pallet::config] pub trait Config: frame_system::Config { type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; } #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(T))] pub struct Kitty<T: Config> { // Using 32 bytes to represent a kitty DNA pub dna: [u8; 32], pub owner: T::AccountId, } #[pallet::storage] pub(super) type CountForKitties<T: Config> = StorageValue<Value = u32, QueryKind = ValueQuery>; #[pallet::storage] pub(super) type Kitties<T: Config> = StorageMap<Key = [u8; 32], Value = Kitty<T>>; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event<T: Config> { Created { owner: T::AccountId }, } #[pallet::error] pub enum Error<T> { TooManyKitties, DuplicateKitty, } #[pallet::call] impl<T: Config> Pallet<T> { pub fn create_kitty(origin: OriginFor<T>) -> DispatchResult { let who = ensure_signed(origin)?; let dna = Self::gen_dna(); Self::mint(who, dna)?; Ok(()) } } } }
#![allow(unused)] fn main() { // Tests for the Kitties Pallet. // // Normally this file would be split into two parts: `mock.rs` and `tests.rs`. // The `mock.rs` file would contain all the setup code for our `TestRuntime`. // Then `tests.rs` would only have the tests for our pallet. // However, to minimize the project, these have been combined into this single file. // // Learn more about creating tests for Pallets: // https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/guides/your_first_pallet/index.html // This flag tells rust to only run this file when running `cargo test`. #![cfg(test)] use crate as pallet_kitties; use crate::*; use frame::deps::sp_io; use frame::runtime::prelude::*; use frame::testing_prelude::*; use frame::traits::fungible::*; type Balance = u64; type Block = frame_system::mocking::MockBlock<TestRuntime>; // In our "test runtime", we represent a user `AccountId` with a `u64`. // This is just a simplification so that we don't need to generate a bunch of proper cryptographic // public keys when writing tests. It is just easier to say "user 1 transfers to user 2". // We create the constants `ALICE` and `BOB` to make it clear when we are representing users below. const ALICE: u64 = 1; const BOB: u64 = 2; const DEFAULT_KITTY: Kitty<TestRuntime> = Kitty { dna: [0u8; 32], owner: 0 }; // Our blockchain tests only need 3 Pallets: // 1. System: Which is included with every FRAME runtime. // 2. PalletBalances: Which is manages your blockchain's native currency. (i.e. DOT on Polkadot) // 3. PalletKitties: The pallet you are building in this tutorial! construct_runtime! { pub struct TestRuntime { System: frame_system, PalletBalances: pallet_balances, PalletKitties: pallet_kitties, } } // Normally `System` would have many more configurations, but you can see that we use some macro // magic to automatically configure most of the pallet for a "default test configuration". #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for TestRuntime { type Block = Block; type AccountData = pallet_balances::AccountData<Balance>; } // Normally `pallet_balances` would have many more configurations, but you can see that we use some // macro magic to automatically configure most of the pallet for a "default test configuration". #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] impl pallet_balances::Config for TestRuntime { type AccountStore = System; type Balance = Balance; } // This is the configuration of our Pallet! If you make changes to the pallet's `trait Config`, you // will also need to update this configuration to represent that. impl pallet_kitties::Config for TestRuntime { type RuntimeEvent = RuntimeEvent; } // We need to run most of our tests using this function: `new_test_ext().execute_with(|| { ... });` // It simulates the blockchain database backend for our tests. // If you forget to include this and try to access your Pallet storage, you will get an error like: // "`get_version_1` called outside of an Externalities-provided environment." pub fn new_test_ext() -> sp_io::TestExternalities { frame_system::GenesisConfig::<TestRuntime>::default() .build_storage() .unwrap() .into() } #[test] fn starting_template_is_sane() { new_test_ext().execute_with(|| { let event = Event::<TestRuntime>::Created { owner: ALICE }; let _runtime_event: RuntimeEvent = event.into(); let _call = Call::<TestRuntime>::create_kitty {}; let result = PalletKitties::create_kitty(RuntimeOrigin::signed(BOB)); assert_ok!(result); }); } #[test] fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { // We often need to set `System` to block 1 so that we can see events. System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); }); } #[test] fn create_kitty_checks_signed() { new_test_ext().execute_with(|| { // The `create_kitty` extrinsic should work when being called by a user. assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); // The `create_kitty` extrinsic should fail when being called by an unsigned message. assert_noop!(PalletKitties::create_kitty(RuntimeOrigin::none()), DispatchError::BadOrigin); }) } #[test] fn create_kitty_emits_event() { new_test_ext().execute_with(|| { // We need to set block number to 1 to view events. System::set_block_number(1); // Execute our call, and ensure it is successful. assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); // Assert the last event by our blockchain is the `Created` event with the correct owner. System::assert_last_event(Event::<TestRuntime>::Created { owner: 1 }.into()); }) } #[test] fn count_for_kitties_created_correctly() { new_test_ext().execute_with(|| { // Querying storage before anything is set will return `0`. assert_eq!(CountForKitties::<TestRuntime>::get(), 0); // You can `set` the value using an `u32`. CountForKitties::<TestRuntime>::set(1337u32); // You can `put` the value directly with a `u32`. CountForKitties::<TestRuntime>::put(1337u32); }) } #[test] fn mint_increments_count_for_kitty() { new_test_ext().execute_with(|| { // Querying storage before anything is set will return `0`. assert_eq!(CountForKitties::<TestRuntime>::get(), 0); // Call `create_kitty` which will call `mint`. assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); // Now the storage should be `1` assert_eq!(CountForKitties::<TestRuntime>::get(), 1); }) } #[test] fn mint_errors_when_overflow() { new_test_ext().execute_with(|| { // Set the count to the largest value possible. CountForKitties::<TestRuntime>::set(u32::MAX); // `create_kitty` should not succeed because of safe math. assert_noop!( PalletKitties::create_kitty(RuntimeOrigin::signed(1)), Error::<TestRuntime>::TooManyKitties ); }) } #[test] fn kitties_map_created_correctly() { new_test_ext().execute_with(|| { let zero_key = [0u8; 32]; assert!(!Kitties::<TestRuntime>::contains_key(zero_key)); Kitties::<TestRuntime>::insert(zero_key, DEFAULT_KITTY); assert!(Kitties::<TestRuntime>::contains_key(zero_key)); }) } #[test] fn create_kitty_adds_to_map() { new_test_ext().execute_with(|| { assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); assert_eq!(Kitties::<TestRuntime>::iter().count(), 1); }) } #[test] fn cannot_mint_duplicate_kitty() { new_test_ext().execute_with(|| { assert_ok!(PalletKitties::mint(ALICE, [0u8; 32])); assert_noop!(PalletKitties::mint(BOB, [0u8; 32]), Error::<TestRuntime>::DuplicateKitty); }) } #[test] fn kitty_struct_has_expected_traits() { new_test_ext().execute_with(|| { let kitty = DEFAULT_KITTY; let bytes = kitty.encode(); let _decoded_kitty = Kitty::<TestRuntime>::decode(&mut &bytes[..]).unwrap(); assert!(Kitty::<TestRuntime>::max_encoded_len() > 0); let _info = Kitty::<TestRuntime>::type_info(); }) } #[test] fn mint_stores_owner_in_kitty() { new_test_ext().execute_with(|| { assert_ok!(PalletKitties::mint(1337, [42u8; 32])); let kitty = Kitties::<TestRuntime>::get([42u8; 32]).unwrap(); assert_eq!(kitty.owner, 1337); assert_eq!(kitty.dna, [42u8; 32]); }) } #[test] fn create_kitty_makes_unique_kitties() { new_test_ext().execute_with(|| { // Two calls to `create_kitty` should work. assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(BOB))); // And should result in two kitties in our system. assert_eq!(CountForKitties::<TestRuntime>::get(), 2); assert_eq!(Kitties::<TestRuntime>::iter().count(), 2); }) } }
diff --git a/src/impls.rs b/src/impls.rs
index 5d3c104..fac2ead 100644
--- a/src/impls.rs
+++ b/src/impls.rs
@@ -1,7 +1,19 @@
use super::*;
use frame::prelude::*;
+/* 🚧 TODO 🚧: Import `frame::primitives::BlakeTwo256`. */
+/* 🚧 TODO 🚧: Import `frame::traits::Hash`. */
impl<T: Config> Pallet<T> {
+ /* 🚧 TODO 🚧: Create a function `gen_dna` which returns a `[u8; 32]`.
+ - Create a `unique_payload` which contains data from `frame_system::Pallet::<T>`:
+ - `parent_hash`
+ - `block_number`
+ - `extrinsic_index`
+ - `CountForKitties::<T>::get()`
+ - Use `BlakeTwo256` to calculate the `hash_of` the unique payload.
+ - Return the hash as a `[u8; 32]`.
+ */
+
pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult {
let kitty = Kitty { dna, owner: owner.clone() };
// Check if the kitty does not already exist in our storage map
diff --git a/src/lib.rs b/src/lib.rs
index aa6ca3b..34507f3 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -48,6 +48,7 @@ pub mod pallet {
impl<T: Config> Pallet<T> {
pub fn create_kitty(origin: OriginFor<T>) -> DispatchResult {
let who = ensure_signed(origin)?;
+ /* 🚧 TODO 🚧: Use the `Self::gen_dna()` function to generate a unique Kitty. */
let dna = [0u8; 32];
Self::mint(who, dna)?;
Ok(())
diff --git a/src/impls.rs b/src/impls.rs
index fac2ead..ff69cae 100644
--- a/src/impls.rs
+++ b/src/impls.rs
@@ -1,18 +1,22 @@
use super::*;
use frame::prelude::*;
-/* 🚧 TODO 🚧: Import `frame::primitives::BlakeTwo256`. */
-/* 🚧 TODO 🚧: Import `frame::traits::Hash`. */
+use frame::primitives::BlakeTwo256;
+use frame::traits::Hash;
impl<T: Config> Pallet<T> {
- /* 🚧 TODO 🚧: Create a function `gen_dna` which returns a `[u8; 32]`.
- - Create a `unique_payload` which contains data from `frame_system::Pallet::<T>`:
- - `parent_hash`
- - `block_number`
- - `extrinsic_index`
- - `CountForKitties::<T>::get()`
- - Use `BlakeTwo256` to calculate the `hash_of` the unique payload.
- - Return the hash as a `[u8; 32]`.
- */
+ // Generates and returns DNA
+ pub fn gen_dna() -> [u8; 32] {
+ // Create randomness payload. Multiple kitties can be generated in the same block,
+ // retaining uniqueness.
+ let unique_payload = (
+ frame_system::Pallet::<T>::parent_hash(),
+ frame_system::Pallet::<T>::block_number(),
+ frame_system::Pallet::<T>::extrinsic_index(),
+ CountForKitties::<T>::get(),
+ );
+
+ BlakeTwo256::hash_of(&unique_payload).into()
+ }
pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult {
let kitty = Kitty { dna, owner: owner.clone() };
diff --git a/src/lib.rs b/src/lib.rs
index 34507f3..8795b0e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -48,8 +48,7 @@ pub mod pallet {
impl<T: Config> Pallet<T> {
pub fn create_kitty(origin: OriginFor<T>) -> DispatchResult {
let who = ensure_signed(origin)?;
- /* 🚧 TODO 🚧: Use the `Self::gen_dna()` function to generate a unique Kitty. */
- let dna = [0u8; 32];
+ let dna = Self::gen_dna();
Self::mint(who, dna)?;
Ok(())
}
diff --git a/src/tests.rs b/src/tests.rs
index 53a248b..e2b802b 100644
--- a/src/tests.rs
+++ b/src/tests.rs
@@ -202,3 +202,15 @@ fn mint_stores_owner_in_kitty() {
assert_eq!(kitty.dna, [42u8; 32]);
})
}
+
+#[test]
+fn create_kitty_makes_unique_kitties() {
+ new_test_ext().execute_with(|| {
+ // Two calls to `create_kitty` should work.
+ assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE)));
+ assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(BOB)));
+ // And should result in two kitties in our system.
+ assert_eq!(CountForKitties::<TestRuntime>::get(), 2);
+ assert_eq!(Kitties::<TestRuntime>::iter().count(), 2);
+ })
+}