Implement a randomness function inside a pallet
Randomness is used in computer programs for many applications. For example, gaming applications, NFT creation, and selecting block authors all require a degree of randomness.
True randomness is hard to come by in deterministic computers. This is particularly true in the context of a blockchain, when all the nodes in the network must agree on the state of the chain. FRAME provides runtime engineers with a source of randomness, using the Randomness trait.
This guide explains how to make use of FRAME's Randomness trait by using the random
method and a nonce as a subject.
The guide also illustrates how to add entropy to the randomness value by assigning the RandomCollectiveFlip
pallet to the configuration trait of a pallet that exposes a "random" type.
Import Randomness
In the pallet you want to use, import the
Randomness
trait fromframe_support
:use frame_support::traits::Randomness;
Include it in your pallet's configuration trait:
#[pallet::config] pub trait frame_system::Config { type MyRandomness: Randomness<Self::Hash, Self::BlockNumber>; }
Note that the
Randomness
trait specifies a generic return of typeOutput
andBlockNumber
. UseBlockNumber
andHash
fromframe_system
in your pallet to satisfy that trait requirement.As stated in this trait's documentation, at best, this trait can give you randomness which was hard to predict a long time ago but that has become easy to predict recently. Keep this in mind when you evaluate your use of it.
Create a nonce and use it in your randomness implementation
Use a nonce to serve as a subject for the frame_support::traits::Randomness::random(subject: &[u8])
method.
There are two steps to including a nonce in your pallet:
Create a
Nonce
storage item. The storage item can be typeu32
oru64
.Create a private nonce function. This function increments the nonce each time it's used.
The
increment_nonce()
private function can be implemented in such a way that it both returns and updates the nonce. For example:fn get_and_increment_nonce() -> Vec<u8> { let nonce = Nonce::<T>::get(); Nonce::<T>::put(nonce.wrapping_add(1)); nonce.encode() }
To learn more about the wrapping and encoding methods, see
wrapping_add
andencode()
in the Rust documentation.Use Randomness in a dispatchable.
Using the nonce, you can call the
random()
method thatRandomness
exposes. The code snippet below is a mock example that assumes relevant events and storage items have been implemented:#[pallet::weight(100)] pub fn create_unique( origin: OriginFor<T>) -> DispatchResultWithPostInfo { // Account calling this dispatchable. let sender = ensure_signed(origin)?; // Random value. let nonce = Self::get_and_increment_nonce(); let (randomValue, _) = T::MyRandomness::random(&nonce); // Write the random value to storage. <MyStorageItem<T>>::put(randomValue); Self::deposit_event(Event::UniqueCreated(randomValue)); }
Update your pallet's runtime implementation.
Because you have added a type to your pallet's configuration trait,
Config
opens up the opportunity to further enhance the randomness derived by theRandomness
trait. This is accomplished by using the Randomness Collective Flip pallet.Using this pallet alongside the
Randomness
trait will significantly improve the entropy being processed byrandom()
.In
runtime/src/lib.rs
, assumingpallet_random_collective_flip
is instantiated inconstruct_runtime
asRandomCollectiveFlip
, specify your exposed type in the following way:impl my_pallet::Config for Runtime{ type Event; type MyRandomness = RandomCollectiveFlip; }