跳转至

智能合约中的随机数

原文:https://docs.elrond.com/developers/developer-reference/random-numbers-in-smart-contracts

区块链环境中的随机性是一项具有挑战性的任务。由于环境的性质,节点必须都具有相同的“随机”生成器,以便能够达成一致。这是通过使用 Golang 的标准种子随机数生成器直接在 VM 中解决的:https://cs . open source . Google/go/go/+/refs/tags/go 1 . 17 . 5:src/math/rand/

VM 函数mBufferSetRandom使用这个库,库的种子是:

  • 前一个块随机种子
  • 当前块随机种子
  • tx 哈希

我们不打算详细讨论 Golang 库如何使用种子或者如何生成随机数,因为这不是本教程的目的。

智能合约中的随机数

为此,ManagedBuffer类型有两种方法可以使用:

  • fn new_random(nr_bytes: usize) -> Self,创建一个新的nr_bytes随机字节的ManagedBuffer
  • fn set_random(&mut self, nr_bytes: usize),将已经存在的缓冲区设置为随机字节

为了方便起见,创建了这些方法的包装器,即RandomnessSource struct,它包含为所有 base rust 无符号数字类型生成随机数的方法,以及生成随机字节的方法。

例如,假设您想要生成n随机数u16:

let mut rand_source = RandomnessSource::<Self::Api>::new();
for _ in 0..n {
    let my_rand_nr = rand_source.next_u16();
    // do something with the number
} 

所有 Rust 无符号数值类型都有类似的方法。

特定范围内的随机数

假设你想在你的智能合约(https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle)中实现一个费希尔-耶茨洗牌算法。

RandomnessSource struct 提供了在一个范围内生成数字的方法,即fn next_usize_in_range(min: usize, max: usize) -> usize,它在[min, max)范围内生成一个随机的usize。这些方法也适用于其余的数字类型,但是对于这个例子,我们需要usize(在 Rust 中,索引是usize)。

为此,你可能会有某种向量。例如,让我们假设你想要打乱一个ManagedBuffer的向量。

let mut my_vec = ManagedVec::new();
// ...
// fill my_vec with elements
// ...

let vec_len = my_vec.len();
let mut rand_source = RandomnessSource::<Self::Api>::new();
for i in 0..vec_len {
    let rand_index = rand_source.next_usize_in_range(i, vec_len);
    let first_item = my_vec.get(i).unwrap();
    let second_item = my_vec.get(rand_index).unwrap();

    my_vec.set(i, &second_item);
    my_vec.set(rand_index, &first_item);
} 

该算法将位置i处的每个元素与位置[i, vec_len)处的一个元素混洗。

随机字节

假设您想在合约中创建一些 NFT,并想给每个 NFT 一个 32 字节的随机散列。为此,您可以使用RandomnessSource结构的next_bytes(len: usize)方法:

let mut rand_source = RandomnessSource::<Self::Api>::new();
let rand_hash = rand_source.next_bytes(32);
// NFT create logic here 

注意事项

警告

永远不要在你的智能合约中包含只依赖于当前状态的逻辑。

实施不当的例子:

#[payable("EGLD")]
#[endpoint(rollDie)]
fn roll_die(&self) {
    // ...
    let payment = self.call_value().egld_value();
    let rand_nr = rand_source.next_u8();
    if rand_nr % 6 == 0 {
        let prize = payment * 2u32;
        self.send().direct(&caller, &prize);
    }
    // ...
} 

这很容易被滥用,因为你可以简单地模拟你的交易,只有当你看到你赢了才发送它们。因此,保证 100%的胜算!

请记住,你不是在你自己的私人服务器上运行,而是在一个公共的区块链上运行,所以你需要一个完整的设计转变。

良好实施的示例:

#[payable("EGLD")]
#[endpoint(signUp)]
fn sign_up(&self) {
    let already_signed_up = self.user_list().insert(caller.clone());
    if already_signed_up {
        sc_panic!("Already signed up");
    }
}

#[only_owner]
#[endpoint(selectWinners)]
fn select_winners(&self) {
    for user in self.user_list().iter() {
        let rand_nr = rand_source.next_u8();
        if rand_nr % 6 == 0 {
            self.winners_list().insert(user.clone());
        }
    }
}

#[endpoint]
fn claim(&self) {
    let was_winner = self.winners_list().swap_remove(&caller);
    if was_winner {
        self.send().direct_egld(&caller, &prize);
    }
}

#[storage_mapper("userList")]
fn user_list(&self) -> UnorderedSetMapper<ManagedAddress>;

#[storage_mapper("winnersList")]
fn winners_list(&self) -> UnorderedSetMapper<ManagedAddress>; 

结论

这个随机数生成器对于大多数目的来说应该足够了。享受使用它为您的彩票等!



回到顶部