智能合约中的随机数
原文: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>;
结论
这个随机数生成器对于大多数目的来说应该足够了。享受使用它为您的彩票等!