David Nasaw
David Nasaw

Reputation: 167

How to get random number in Solana on-chain program?

I have just jumped in Solana on-chain program. I am going to make coin game which judge frontside or backside. I tried to use std:: rand and get_random crate but they don't work. If you have experience about it, please let me know.

I use anchor for Solana on-chain program.

Upvotes: 6

Views: 4819

Answers (2)

Yilmaz
Yilmaz

Reputation: 49431

chainlink vrf(verifiable random function) is not available yet on Solana. you could use https://docs.switchboard.xyz/solana/dev/rust

use anchor_lang::prelude::*;
use anchor_lang::solana_program::clock;
use std::convert::TryInto;
pub use switchboard_v2::{AggregatorAccountData, SwitchboardDecimal, SWITCHBOARD_PROGRAM_ID};

declare_id!("FnsPs665aBSwJRu2A8wGv6ZT76ipR41kHm4hoA3B1QGh");

#[derive(Accounts)]
#[instruction(params: ReadResultParams)]
pub struct ReadResult<'info> {
    pub aggregator: AccountLoader<'info, AggregatorAccountData>,
}

#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
pub struct ReadResultParams {
    pub max_confidence_interval: Option<f64>,
}

#[program]
pub mod anchor_feed_parser {
    use super::*;

    pub fn read_result(
        ctx: Context<ReadResult>,
        params: ReadResultParams,
    ) -> anchor_lang::Result<()> {
        let feed = &ctx.accounts.aggregator.load()?;

        // get result
        let val: f64 = feed.get_result()?.try_into()?;

        // check whether the feed has been updated in the last 300 seconds
        feed.check_staleness(clock::Clock::get().unwrap().unix_timestamp, 300)
            .map_err(|_| error!(FeedErrorCode::StaleFeed))?;

        // check feed does not exceed max_confidence_interval
        if let Some(max_confidence_interval) = params.max_confidence_interval {
            feed.check_confidence_interval(SwitchboardDecimal::from_f64(max_confidence_interval))
                .map_err(|_| error!(FeedErrorCode::ConfidenceIntervalExceeded))?;
        }

        msg!("Current feed result is {}!", val);

        Ok(())
    }
}

#[error_code]
#[derive(Eq, PartialEq)]
pub enum FeedErrorCode {
    #[msg("Not a valid Switchboard account")]
    InvalidSwitchboardAccount,
    #[msg("Switchboard feed has not been updated in 5 minutes")]
    StaleFeed,
    #[msg("Switchboard feed exceeded provided confidence interval")]
    ConfidenceIntervalExceeded,
}

Upvotes: 0

Setmax
Setmax

Reputation: 1034

unfortunately, The random generator doesn't work on-chain. if you want some randoms, you should get it from outside the chain.

why?

assume you making randoms using block hash or something similar, so users can exploit that by inserting an instruction or values that checks for favorable outcomes and even worse, forcing it to roll back if it's not satisfactory.

so what should we do?

  1. try to use oracles like chainlink vrf(verifiable random function)

  2. simulate oracle vrf services:

make transaction on-chain that your server listens to it. if this transaction happened, make random number off-chain and callback it from your server.

anchor use randoms like this

use rand::rngs::OsRng;
.
.
.
let dummy_a = Keypair::generate(&mut OsRng);

so, if you require randomness for UUID-like behavior you can use a mechanism like the above from the anchor code repository but if your case is game logic like a dice roll, you need oracles or simulate that.

UPDATE 11/10/2022

As we know, the Metaplex candy machine tool uses randoms to select the next item to mint.

see this code snippet:

// (2) selecting an item to mint

let recent_slothashes = &ctx.accounts.recent_slothashes;
let data = recent_slothashes.data.borrow();
let most_recent = array_ref![data, 12, 8];

let clock = Clock::get()?;
// seed for the random number is a combination of the slot_hash - timestamp
let seed = u64::from_le_bytes(*most_recent).saturating_sub(clock.unix_timestamp as u64);

let remainder: usize = seed
    .checked_rem(candy_machine.data.items_available - candy_machine.items_redeemed)
    .ok_or(CandyError::NumericalOverflowError)? as usize;

let config_line = get_config_line(candy_machine, remainder, candy_machine.items_redeemed)?;

candy_machine.items_redeemed = candy_machine
    .items_redeemed
    .checked_add(1)
    .ok_or(CandyError::NumericalOverflowError)?;

The idea is you can get blockhash and clock from Solana network as random input seeds to create the next random number.

another code snippet which will be good point to start creating randoms:

//convert blockhash to random seed string
let recent_blockhash_data = recent_blockhashes.data.borrow();
let most_recent = array_ref![recent_blockhash_data, 0, 16];
let some_numbers = u128::from_le_bytes(*most_recent);
let blockhash_random_seed: String = (some_numbers).to_string();

in the above code, we use recent blockhash and convert it to hex(as string). you can select each part of this hexadecimal hash string and use it as random value.

In the end, this is important to know that blockhash is deprecated now and Metaplex use slothash but if you look closer you can see they still use blockhash and just use the name of the variable as slothash.

cheers

Upvotes: 13

Related Questions