LOADING...
LOADING...
LOADING...
当前位置: 玩币族首页 > 行情分析 > 【Substrate开发教程】23 - 如何在基于Substrate的区块链上生成随机数?

【Substrate开发教程】23 - 如何在基于Substrate的区块链上生成随机数?

2020-11-07 松果 来源:区块链网络


这篇文章介绍如何在Substrate Runtime生成(不安全的)随机性。

随机性在计算机程序中非常有用,区块链中的以下应用场景就经常用到随机性:菠菜游戏、CryptoKitties生成小猫的DNA、选择区块生产者等。

但要在确定性的计算机中生成真正的随机性是很难的,因此通过计算机生成的随机数本质上是一种伪随机数(pseudo random number),只是尽可能地“随机”。

目前已经开发出一些技术来解决这个问题,比如RanDAO和VRF(Verifiable Random Functions,可验证随机函数)。

Substrate使用frame_support::traits::Randomness抽象了随机性源的实现,并提供了一些实现,下面的代码演示如何使用Randomness特征和其实现。

编写模块代码

修改pallets/template/src/lib.rs的代码如下:

#![cfg_attr(not(feature = "std"), no_std)] use frame_support::{ decl_event, decl_module, decl_storage, dispatch::DispatchResult, traits::Randomness, }; use frame_system::{self as system, ensure_signed}; use codec::Encode; use sp_core::H256; use sp_std::vec::Vec; pub trait Trait: system::Trait { type Event: From<Event> + Into<<Self as system::Trait>::Event>; type RandomnessSource: Randomness<H256>; } decl_storage! { trait Store for Module<T: Trait> as RandomnessPallet { Nonce get(fn nonce): u32; } } decl_event!( pub enum Event { RandomnessConsumed(H256, H256), } ); decl_module! { pub struct Module<T: Trait> for enum Call where origin: T::Origin { fn deposit_event() = default; #[weight = 10_000] fn consume_randomness(origin) -> DispatchResult { let _ = ensure_signed(origin)?; let subject = Self::encode_and_update_nonce(); let random_seed = T::RandomnessSource::random_seed(); let random_result = T::RandomnessSource::random(&subject); Self::deposit_event(Event::RandomnessConsumed(random_seed, random_result)); Ok(()) } } } impl<T: Trait> Module<T> { fn encode_and_update_nonce() -> Vec<u8> { let nonce = Nonce::get(); Nonce::put(nonce.wrapping_add(1)); nonce.encode() } }

下面详细分析模块代码。

配置Cargo.toml

此模块使用了sp-core、sp_runtime、sp-std模块,需要在Cargo.toml中进行配置。

[dependencies]下添加:

sp-core = { version = '2.0.0', default-features = false } sp-runtime = { version = '2.0.0', default-features = false } sp-std = { version = '2.0.0', default-features = false }

[features]的std数组下添加:

'sp-core/std', 'sp-runtime/std',

配置Runtime

下面配置Runtime,修改runtime/src/lib.rs的代码。

修改第265行代码:

impl pallet_template::Trait for Runtime { type Event = Event; }

改为:

impl pallet_template::Trait for Runtime { type Event = Event; type RandomnessSource = CollectiveFlip; } pub type CollectiveFlip = pallet_randomness_collective_flip::Module<Runtime>;

模块代码中pub trait Trait: system::Trait{...}中声明的类型,需要在此处定义。

修改第285行代码:

TemplateModule: pallet_template::{Module, Call, Storage, Event<T>},

改为:

TemplateModule: pallet_template::{Module, Call, Storage, Event},

此模块只使用基本事件,需要去掉泛型<T>。

Randomness Trait

Randomness特征提供了两个方法:

fn random_seed() -> Outputfn random(subject: &[u8]) -> Output

这两个方法均提供了traits类型参数中指定的类型的伪随机值。

random_seed方法不带任何参数,返回一个随机种子,该种子在每个区块更改一次,如果在同一个区块中调用此方法两次会得到相同的结果。

random方法采用字节数组&[u8]类型作为参数,与随机种子一起使用以计算最终的随机值,这种方式可以在同一个区块中获得不同的随机值。

&[u8]类型的参数称为主题(subject),用于主题的常见值包括:

区块编号;调用者的AccountId;一个nonce;一个模块指定的标识符;一个包含以上几种类型的元组;

声明随机性源

pub trait Trait: system::Trait { type Event: From<Event> + Into<<Self as system::Trait>::Event>; type RandomnessSource: Randomness<H256>; }

声明随机性源类型RandomnessSource,指定输出类型为Struct sp_core::H256。

调用随机性源

fn consume_randomness(origin) -> DispatchResult { let _ = ensure_signed(origin)?; let subject = Self::encode_and_update_nonce(); let random_seed = T::RandomnessSource::random_seed(); let random_result = T::RandomnessSource::random(&subject); Self::deposit_event(Event::RandomnessConsumed(random_seed, random_result)); Ok(()) }

这里主题参数是由encode_and_update_nonce()函数提供的,它的定义如下:

fn encode_and_update_nonce() -> Vec<u8> { let nonce = Nonce::get(); Nonce::put(nonce.wrapping_add(1)); nonce.encode() }

只是简单的加1。

采用哪种随机性源算法实现在runtime/src/lib.rs中定义,默认使用的是randomness_collective_flip模块提供的算法。

也可以使用babe模块提供的VRF随机性源算法,相应的业务逻辑代码该改成:

let random_seed = T::BabeRandomnessSource::random_seed(); let random_result = T::BabeRandomnessSource::random(&subject);

在生产环境中,一般使用Babe VRF而不是Collective Flip。

编译

编译命令如下:

cd substrate-node-template cargo +nightly-2020-08-23 build --release

使用了nightly-2020-08-23这个较稳定的cargo nightly版本以避免编译过程中出现bug。


测试

启动node-template

./target/release/node-template --dev

打开https://polkadot.js.org/apps,切换网络为DEVELOPMENT-Local Node:


选项卡选择开发者-交易,“提交下面的外部信息”选择templateModule,会自动获取到定义的函数consumeRandomness():


这里选择BOB账户作为consumeRandomness()函数的调用者。

点击右下角“提交交易”按钮,点击“签名并提交”:


就会调用decl_module!中定义的consume_randomness函数,并触发事件RandomnessConsumed。

选项卡选择网络-浏览-链信息,在右侧可以看到最新触发的事件:


可以看到触发了模块代码中定义的templateModule.RandomnessConsumed事件,这里触发了两次,每次广播的数据分别是random_seed和random_result。

选项卡选择开发者-链状态-存储,“查询所选状态”选择templateModule,查看nonce(): u32中数据:


可以查看目前生成随机数的次数。

—-

编译者/作者:松果

玩币族申明:玩币族作为开放的资讯翻译/分享平台,所提供的所有资讯仅代表作者个人观点,与玩币族平台立场无关,且不构成任何投资理财建议。文章版权归原作者所有。

LOADING...
LOADING...