Search code examples
randomblockchainsmartcontractssolanacontract

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.


Solution

  • 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