总所周知的原因,在区块链里实现真随机数是不可能的。只能实现非常假的为随机数。
另一方面,区块链合约执行的时间,并不是实时的,而是创建区块的时间。
比如 EOS 中的 now()
和 current_time()
两个时间都是创块的时间,在同一个块内,所有事务的时间都是一样的。
为了防止随机数被猜中,最好的实现方案,就是产生随机数的动作延时执行,比如 1s 或者 0.5s
那么,本章,我们就来讨论如是实现一个事务或者动作的延时,准确的说,是实现一个动作的延时。
EOS 创建延时动作
什么是延时动作
我们都知道,EOS 中最基本的组成单位是 动作 action
,一个事务,由 0 个或者多个动作组成一个事务/交易,然后矿工会将多个事务打包成一个块。
EOS 的出块时间大概是 0.5s/个。
有了这个前提,我们可以清晰的看到,所谓的延时动作,就是创建一个 动作 ,然后把这个动作放在一个事务/交易里,设定交易的执行时间延时,最后签名并发送这个交易即可。
相关库和函数
EOSIO 的开发者工具 eosio.cdt 提供了两个和事务/交易相关的文件 <eosiolib/transaction.hpp>
和 <eosiolib/transaction.h>
其中 <eosiolib/transaction.h>
文件主要包含了读取和解析事务/交易的函数,而 <eosiolib/transaction.hpp>
则包含了如何创建事务以及发送事务的函数。
我们今天的重点是创建和发送延时事务,因此,我们的研究对象就是 <eosiolib/transaction.hpp>
<eosiolib/transaction.hpp>
包含了一个事务的头部 transaction_header
和一个事务类 transaction
,且事务类 transaction
继承自事务头部 transaction_header
class transaction_header { public: transaction_header( time_point_sec exp = time_point_sec(now() + 60) ) :expiration(exp) {} time_point_sec expiration; uint16_t ref_block_num; uint32_t ref_block_prefix; unsigned_int max_net_usage_words = 0UL; /// number of 8 byte words this transaction can serialize into after compressions uint8_t max_cpu_usage_ms = 0UL; /// number of CPU usage units to bill transaction for unsigned_int delay_sec = 0UL; /// number of seconds to delay transaction, default: 0 EOSLIB_SERIALIZE( transaction_header, (expiration)(ref_block_num)(ref_block_prefix)(max_net_usage_words)(max_cpu_usage_ms)(delay_sec) ) }; class transaction : public transaction_header { public: transaction(time_point_sec exp = time_point_sec(now() + 60)) : transaction_header( exp ) {} void send(const uint128_t& sender_id, name payer, bool replace_existing = false) const { auto serialize = pack(*this); send_deferred(sender_id, payer.value, serialize.data(), serialize.size(), replace_existing); } std::vector<action> context_free_actions; std::vector<action> actions; extensions_type transaction_extensions; EOSLIB_SERIALIZE_DERIVED( transaction, transaction_header, (context_free_actions)(actions)(transaction_extensions) ) };
从上面的源代码中可以看出
- 事务头部有一个参数叫做
delay_sec
,用于表示事务的延时执行时间,默认值为0L
,也就是不延时 - 事务类中
transaction
有且只有一个方法send()
,用于发送该事务。
那么,要实现一个延时事务就很简单了。简单几句代码就解决了
-
引入事务头文件
#include<eosiolib/transaction.hpp>
-
初始化一个事务
eosio::transaction t{};
-
往事务里压入一个动作
t.actions.emplace_back( permission_level(_self,"active"_n), _self, "lottery"_n, std::make_tuple(_self,id) );
-
设置动作的延时,单位为秒
t.delay_sec = 2; // 延时 2 s
-
发送事务
t.send(sender_id,_self);
这里需要注意的是
sender_id
的值,如果发送了相同的sender_id
值,那么在第三个参数replace_existing
设定为true
的时候,那么后面的相同sender_id
动作会替换原先的动作。 切记切记
范例
下面的范例,创建了一个 hello
的合约,并在合约动作 hi
中创建一个延时事务,延时 2s 调用 world
动作
#include <eosiolib/eosio.hpp> #include <eosiolib/print.hpp> #include <eosiolib/transaction.hpp> #include <eosiolib/action.hpp> #include <eosiolib/system.h> using namespace eosio; class hello : public contract { public: using contract::contract; [[eosio::action]] void hi() { print("Hello "); // 创建延时动作 eosio::transaction t{}; t.actions.emplace_back( permission_level(_self,"active"_n), _self, "world"_n, std::make_tuple(_self,std::string("yufei")) ); t.delay_sec = 2; // 延时 2 s t.send(current_time(),_self); } [[eosio::action]] void world(name from, std::string memo) { print(" World! "); print("Hello "); print(std::string(memo)); } }; EOSIO_DISPATCH( hello, (hi)(world))