如何在Solana on-chain程序中获取随机数?

5

我刚参加了Solana on-chain的编程项目。 我计划开发一个硬币游戏,可以判断正面或反面。 我尝试使用std:: rand和get_random crate但它们都无法正常工作。 如果你有相关经验,请告诉我。

我在Solana on-chain项目中使用anchor。


你好!我没有Solana的经验,但你应该发布一些代码,这样人们可以更好地帮助你。 - adrisons
嗨,@David Nasaw,你是如何解决这个问题的?能分享一下你的代码示例吗? - marethyu
2个回答

10

遗憾的是,随机生成器不能在链上工作。 如果你需要一些随机数,你应该从链外获取。

为什么?

假设您使用块哈希或类似的东西生成随机数,那么用户可以通过插入检查有利结果的指令或值来利用它,甚至更糟的是,如果不满意,强制回滚。

那我们该怎么办?

  1. 尝试使用像chainlink vrf(可验证随机函数)这样的预言机。

  2. 模拟预言机vrf服务:

在链上进行一个交易,让您的服务器监听它。如果发生此交易,请在链外生成随机数并从您的服务器回调它。

Anchor就是这样使用随机数的。

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

因此,如果您需要UUID类的行为所需的随机性,则可以使用类似上面的锚定代码库中的机制,但如果您的情况是游戏逻辑(例如掷骰子),则需要使用预言机或者模拟。

2022年11月10日更新

我们知道,Metaplex糖果机工具使用随机数来选择下一个要铸造的物品。

请参见此代码片段:

// (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)?;

这个想法是你可以从Solana网络中获取区块哈希和时钟作为随机输入种子,以创建下一个随机数。

另一个代码片段将是创建随机数的好起点:

//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();

在上述代码中,我们使用最近的块哈希并将其转换为十六进制(字符串)。你可以选择此十六进制哈希字符串的每个部分并将其用作随机值。
最后,需要知道的是,blockhash 已经过时了,Metaplex 使用 slothash,但如果你仔细看,你会发现他们仍然使用 blockhash,只是将变量名用作 slothash。
干杯

嗨@Setmax,你有没有不使用vrf服务的示例代码? - marethyu
嗨@marethyu,你试过答案中的最后一段代码吗? - Setmax
@Setmax使用metaplex方式,它可以被利用吗?虽然使用slothash?因为我有一个案例需要使用随机数来进行随机化,我从metaplex nft包中找到了一个示例代码,它似乎是使用slothash和时间戳的组合。 - Alfian Dwi Nugraha
这取决于你的情况。如果你的情况涉及并发性(一群活跃用户根据随机数攻击以打开一些神秘盒子),你可能需要移除时间戳并添加其他值,比如神秘盒子的铸造地址(如果你的盒子是NFTs)。 - Setmax

0

在Solana上,Chainlink VRF(可验证随机函数)尚不可用。您可以使用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,
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接