Generating On-chain Randomness
Goal
Implement a randomness function inside a pallet.
Use Cases
- NFT applications
- Casino gaming type applications
Overview
Randomnessis useful in computer programs for everything from gaming applications to selecting block
authors. 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 would step you through making use of FRAME's Randomness trait by using it's random
method and a nonce as a subject.
For additional entropy to the randomness value, the last step shows how to assign the RandomCollectiveFlip
pallet
to the configuration trait of a pallet exposing some "random" type.
Steps
1. Import Randomness
Inside the pallet you want to use, import the Randomness
trait from frame_support
,:
use frame_support::traits::Randomness;
Now, include it in your pallet's configuration trait:
#[pallet::config]
pub trait frame_system::Config {
type MyRandomness: Randomness<H256>;
}
Notice that the Randomness
trait specifies a generic return of type Output
. Use sp_core::H256
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. Bear this in mind depending on your use case for it.
2. Create a nonce
Use a nonce to serve as a subject for the frame_support::traits::Randomness::random(subject: &[u8])
method.
There's two steps to including a nonce in your pallet:
- Create a
Nonce
storage item. This could be of typeu32
oru64
(no need for it to be larger). - Create a private nonce function. This will be used to increment the nonce each time it's used.
The increment_nonce()
private function could be implemented in such a way that it returns the nonce as well as
updates it. Using this approach it would look like this:
fn get_and_increment_nonce() -> Vec<u8> {
let nonce = Nonce::get();
Nonce::put(nonce.wrapping_add(1));
nonce.encode()
}
Learn more about wrapping_add
and encode()
in the
Rust documentation.
3. Use Randomness in a dispatchable
Using the nonce, you can call the random()
method that Randomness
exposes. The code snippet below is a made up 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));
}
4. Updating your pallet's runtime implementation
Having added a type to your pallet's configuration trait Config
opens up the opportunity to further enhance the
randomness derived by the Randomness
trait, by using the Randomness Collective Flip pallet.
Using this pallet alongside the Randomness
trait will significantly improve the entropy being processed by random()
.
In runtime/src/lib.rs
, assuming pallet_random_collective_flip
is instantiated in construct_runtime
as RandomCollectiveFlip
, specify your exposed
type like this:
impl my_pallet::Config for Runtime{
type Event;
type MyRandomness = RandomCollectiveFlip;
}