本文档从区块时间基本概念出发,介绍了中移链的区块时间接口和应用方向。适用于 EOS 智能合约的高级开发人员,熟悉如何获取当前区块时间、下一个区块的区块时间、时间戳的转换等。
01
概述
(一)时间戳
时间戳是标识特定事件何时发生的字符序列或编码信息。如今,该术语的用法已经扩展到指附加在数字信息上的数字日期和时间信息。例如,计算机文件包含时间戳,用于提示文件最后一次修改的时间。然而随着数字化文档的诞生,电子数据具有脆弱性、易变性、隐蔽性、载体多样性等特点,容易被复制、删除、篡改且难以被发现。因此,电子数据在实际的司法认定过程中,很难准确坚定其生成的时间以及内容的真实性、完整性。
(二)区块链时间戳
区块链时间戳系统实际就是在 P2P 网络上通过节点间的共识算法实现的分布式时间戳服务。它是利用时间戳实现在时间上有序的、由一个个区块组成的一根链条。每一个新区块生成时,都会被打上时间戳,最终依照区块生成时间的先后顺序相连成区块链,每个独立节点又通过 P2P 网络建立联系,这样就为信息数据的记录形成了一个去中心化的分布式时间戳服务系统。
(三)区块链时间戳特点
因区块链拥有以下特点,使得时间戳可以完全信任:(1)不可销毁/修改;(2)Block 具有天然时间特性,时间戳是 Block meta 字段之一;(3)Block 可以存储交易信息,交易是可以“写入”的数据;时间戳使得更改一条记录的困难程度按时间的指数倍增加,越老的记录越难修改。
02
环境依赖
https://github.com/eosio/eos/releases/download/v2.1.0/eosio_2.1.0-1-ubuntu-18.04_amd64.deb
https://github.com/EOSIO/eosio.cdt/releases/tag/v1.8.1
03
区块时间类与接口
与区块时间相关的类主要为:
eosio::time_point
eosio::time_point_sec
eosio::block_timestamp
其中 block_timestamp 为区块时间戳,time_pointUnix 纪元时间戳(毫秒级精度),time_point_secUnix 纪元时间戳(秒级精度)。
可以通过外部接口获取时间进行业务处理,其中 current_block_time()和 current_time_point()主要用于对区块时间的操作,分别返回 block_timestamp 实例和 time_point 实例。expiration()主要用于对区块内交易时间的操作,返回 uint32_t 整型。
(一)外部接口
1、current_block_time()
获取当前块的 Unix 纪元时间戳(block_timestamp 实例,以微秒为单位)。
#include<eosio/time.hpp>
#include<eosio/system.hpp>
auto cur_timestamp = current_block_time();
复制代码
2、current_time_point()
获取当前块的 Unix 纪元时间戳(time_point 实例,以微秒为单位)。
#include<eosio/time.hpp>
#include<eosio/system.hpp>
auto cur_timepoint = current_time_point();
复制代码
3、expiration()
获取当前交易的 Unix 纪元时间戳(uint32_t,以秒为单位)。
#include<eosio/transaction.hpp>
auto trx_time = eosio::expiration();
transaction_header _trx_header;
复制代码
4、外部接口对比详解
current_block_time()和 current_time_point()两者代表意义均为区块时间,对应于区块结构中的 timestamp,区别只在于具体实例不同。(注:timestamp 可以表示半秒,而 block_timestamp 和 time_point 类中提供的 to_string()函数只能输出精度为秒的字符串格式时间,虽然时间戳的精度可以表示半秒)
current_block_time()获取当前区块时间(block_timestamp 类实例),并提供相关操作函数,next()获取下一个块的区块时间(block_timestamp 类实例),通过转换成 time_point 实例转换成整型时间戳,进行业务运算或输出时间戳。
current_time_point()直接获取当前区块时间的(time_point 实例),可进行业务运算或输出时间戳,time_point 实例可以通过 block_timestamp 类的构造函数转换成 block_timestamp 类实例。
expiration()获取当前 action 隶属交易的时间戳,对应区块结构中 transaction 中的 expiration。
以下为区块结构(含交易):
jasmine@Jasmine:~/eosio/eos/build/bin$ ./cleos get block 110
{
"timestamp": "2023-03-31T01:31:15.000",
"producer": "eosio",
"confirmed": 0,
"previous": "0000006d5b29b623251bea57a48155377081a1b34fa3be9aa5cde21e7d50cb18",
"transaction_mroot": "706eb8ba97278c9f2a6a5ca4c255394403a3abd60bdd6e09265101d9d0626f3c",
"action_mroot": "357eb8fba52b02a68930c6d577dbc53523463534026c60100bf84f28b2ad42ae",
"schedule_version": 0,
"new_producers": null,
"producer_signature": "SIG_K1_KgCxbksk9ttikb1WkLvaAtv9doCcjD9CAgBznTyitbfPNWfkQfPrQpW8NE2WPqmNbJx8onM23uKSmRi3X3zfH5PS2LXstu",
"transactions": [{
"status": "executed",
"cpu_usage_us": 40787,
"net_usage_words": 25,
"trx": {
"id": "e2205dd5cfd536ac07adde8bdf9fa341cc736a5751ec4f6aadd17e8ec6601dcf",
"signatures": [
"SIG_K1_KVxTaDLMu65ZrPnkrtxmZe7BrjdBbF3XztEdYNo9XkN8SJAnPF3cHoMyC5PWKUCF7UCqQgRi6etMTF3dnjooEVEe7QFA4b"
],
"compression": "none",
"packed_context_free_data": "",
"context_free_data": [],
"packed_trx": "003826646c009a80664500000000010000000000ea305500409e9a2264b89a010000000000ea305500000000a8ed3232660000000000ea3055000000008440a54a01000000010002c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf0100000001000000010002c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf0100000000",
"transaction": {
"expiration": "2023-03-31T01:31:44",
"ref_block_num": 108,
"ref_block_prefix": 1164345498,
"max_net_usage_words": 0,
"max_cpu_usage_ms": 0,
"delay_sec": 0,
"context_free_actions": [],
"actions": [{
"account": "eosio",
"name": "newaccount",
"authorization": [{
"actor": "eosio",
"permission": "active"
}
],
"data": {
"creator": "eosio",
"name": "demo11",
"owner": {
"threshold": 1,
"keys": [{
"key": "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV",
"weight": 1
}
],
"accounts": [],
"waits": []
},
"active": {
"threshold": 1,
"keys": [{
"key": "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV",
"weight": 1
}
],
"accounts": [],
"waits": []
}
},
"hex_data": "0000000000ea3055000000008440a54a01000000010002c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf0100000001000000010002c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf01000000"
}
]
}
}
}
],
"id": "0000006e062a01ae4fa1829b16a28dc1cb19b253c4612541590d7d25e9f0fa53",
"block_num": 110,
"ref_block_prefix": 2609029455
}
复制代码
(二)block_timestamp 类
1、block_timestamp()
构造函数
block_timestamp(const time_point& t) {
set_time_point(t);
}
block_timestamp(const time_point_sec& t) {
set_time_point(t);
}
复制代码
2、block_interval_ms
EOS 区块链中,每间隔 0.5 秒进行一次出块,block_interval_ms 为区块间隔(500 毫秒)
static constexpr int32_t block_interval_ms = 500;
复制代码
3、from_iso_string()
将字符串格式时间转为 block_timestamp 实例(注:传入字符串格式为 %Y-%m-%dT%H:%M:%S,精度为秒)
static block_timestamp from_iso_string(const std::string& date_str) {
auto time_p = time_point::from_iso_string(date_str);
return block_timestamp{ time_p };
}
复制代码
4、next()
返回下一个块的出块时间(block_timestamp 实例)
下一个块的出块时间=当前块时间+block_interval_ms
block_timestamp next() const {
eosio::check( std::numeric_limits<uint32_t>::max() - slot >= 1, "block timestamp overflow" );
auto result = block_timestamp(*this);
result.slot += 1;
return result;
}
复制代码
5、to_time_point()
返回 time_point 实例
time_point to_time_point() const {
return (time_point)(*this);
}
复制代码
6、to_string()
返回时间字符串(注:字符串格式为 %Y-%m-%dT%H:%M:%S,因精度为秒而出块间隔为 500 毫秒,部分区块转 string 有误差损失)
std::string to_string() const {
return to_time_point().to_string();
}
复制代码
7、测试用例
/*
测试block_timestamp类,由current_block_time()获取当前区块时间的block_timestamp实例,并进行如下测试:
测试获取静态变量区块间隔时间block_interval_ms
测试from_iso_string()验证时间转换精度
测试next()获取下一个区块时间戳
测试to_time_point()转换block_timestamp实例为time_point实例
测试to_string()返回时间字符串
测试block_timestamp重载运算符
*/
ACTION tstime::gettime(){
block_timestamp current_timestamp = current_block_time(); //获取当前区块时间的block_timestamp实例
//测试获取静态变量区块间隔时间block_interval_ms
int32_t ms = eosio::block_timestamp::block_interval_ms; //current_timestamp.block_interval_ms:500(毫秒)
//测试from_iso_string()验证时间转换精度
auto my_timestamp = eosio::block_timestamp::from_iso_string("2023-03-31T01:31:15.000").to_time_point().time_since_epoch().count(); //时间戳:1680226275000000
auto my_timestamp3 = eosio::block_timestamp::from_iso_string("2023-03-31T01:31:15.999").to_time_point().time_since_epoch().count(); //时间戳:1680226275000000(微秒) 结果相同,时间字符串的解析精度为秒
//测试next()验证下一个区块的时间戳与当前区块时间戳关系
auto next_interval = current_timestamp.next().to_time_point() - current_timestamp.to_time_point(); //500 下一个区块与当前区块的间隔时间
// 测试to_time_point()转换block_timestamp实例为time_point实例
auto cur_timestamp_timepoint = current_timestamp.to_time_point(); //当前区块block_timestamp实例
auto next_timestamp_timepoint = current_timestamp.next().to_time_point(); //下一个区块block_timestamp实例
//测试to_string()返回时间字符串
eosio::print("block_timestamp is:",current_timestamp.to_string(),"\t"); //返回时间字符串2023-05-05T08:13:15
//测试block_timestamp实例的重载运算符
check(next_timestamp > current_timestamp,"next_timestamp <= current_timestamp");
check(next_timestamp < current_timestamp,"next_timestamp >= current_timestamp");
check(next_timestamp != current_timestamp,"next_timestamp == current_timestamp");
check(next_timestamp >= current_timestamp,"next_timestamp < current_timestamp");
}
}
复制代码
(三)time_point 类
1、time_point()
构造函数
explicit time_point( microseconds e = microseconds() ) :elapsed(e){}
复制代码
2、elapsed
当前实例的过期时间(单位微秒)
3、from_iso_string()
将字符串格式时间转为 time_point 实例(注:传入字符串格式为 %Y-%m-%dT%H:%M:%S,精度为秒)
static time_point from_iso_string(const std::string& date_str) {
std::tm tm;
check(strptime(date_str.c_str(), "%Y-%m-%dT%H:%M:%S", &tm), "date parsing failed");
auto tp = std::chrono::system_clock::from_time_t( ::mktime( &tm ) );
auto duration = std::chrono::duration_cast<std::chrono::microseconds>( tp.time_since_epoch() );
return time_point{ microseconds{ static_cast<int64_t>(duration.count()) } };
}
复制代码
4、time_since_epoch()
返回当前实例的 Unix 纪元时间戳(精度:微秒)
const microseconds& time_since_epoch()const { return elapsed; }
复制代码
5、sec_since_epoch()
返回当前实例的 Unix 纪元时间戳(精度:秒)
uint32_t sec_since_epoch()const { return uint32_t(elapsed.count() / 1000000); }
复制代码
6、to_string()
返回时间字符串(注:字符串格式为 %Y-%m-%dT%H:%M:%S,因精度为秒而出块间隔为 500 毫秒,部分区块转 string 有误差损失)
std::string to_string() const {
time_t rawtime = sec_since_epoch();
char buf[100];
strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S", gmtime ( &rawtime ));
return std::string{buf};
}
复制代码
7、测试用例
/*
测试time_point类,current_time_point()获取当前区块时间的time_point实例,并进行如下测试:
测试获取变量过期时间elapsed
测试from_iso_string()验证时间转换精度
测试time_point实例转换为block_timestamp实例
测试sec_since_epoch()的返回时间戳精度
测试time_since_epoch()的返回时间戳精度
测试to_string()返回时间字符串
测试time_point重载运算符
*/
ACTION tstime::gettime(){
time_point block_time_point = current_time_point(); //获取当前区块time_point实例
//测试获取变量过期时间elapsed
auto cur_elapsed = block_time_point.elapsed.count(); // 当前区块time_point实例时间戳 1683274397500000(微秒)
//测试from_iso_string()验证时间转换精度
auto my_time_point = eosio::time_point::from_iso_string("2023-03-31T01:31:15.000").time_since_epoch().count();//时间戳:1680226275000000(微秒)
auto my_time_point2 = eosio::time_point::from_iso_string("2023-03-31T01:31:15.999").time_since_epoch().count();//时间戳:1680226275000000(微秒)结果相同,时间字符串的解析精度为秒
//测试time_point实例转换为block_timestamp实例
auto blocktimestamp_trs = eosio::block_timestamp(block_time_point); //time_point 转block_timestamp
//测试sec_since_epoch()的返回时间戳精度
uint32_t cur_time_point_sec_since = block_time_point.sec_since_epoch(); //返回时间戳1683274397(秒)
//测试time_since_epoch()的返回时间戳精度
int64_t cur_time_point_time_since = block_time_point.time_since_epoch().count(); //返回时间戳1683274397500000(微秒)
//测试to_string()返回时间字符串
eosio::print("block_time_point is:",block_time_point.to_string(),"\t"); //返回时间字符串2023-05-05T08:13:15
//测试time_point重载运算符
eosio::time_point resadd1 = my_time_point4 + my_time_point;
eosio::time_point resadd2 = my_time_point4 + my_time_point.elapsed;
eosio::time_point ressub1 = my_time_point4 - my_time_point.elapsed;
eosio::microseconds ressub2 = my_time_point4 - my_time_point;
check(my_time_point4 > my_time_point,"my_time_point4 <= my_time_point");
check(my_time_point4 < my_time_point,"my_time_point4 >= my_time_point");
check(my_time_point4 != my_time_point,"my_time_point4 == my_time_point");
check(my_time_point4 >= my_time_point,"my_time_point4 < my_time_point");
}
}
复制代码
(四)time_point_sec 类
1、time_point_sec()
构造函数
time_point_sec():
utc_seconds(0){}
explicit time_point_sec(uint32_t seconds )
:utc_seconds(seconds){}
time_point_sec( const time_point& t )
:utc_seconds( uint32_t(t.time_since_epoch().count() / 1000000ll) ){}
复制代码
2、from_iso_string()
将字符串格式时间转为 time_point 实例(注:传入字符串格式为 %Y-%m-%dT%H:%M:%S,精度为秒)
static time_point_sec from_iso_string(const std::string& date_str) {
auto time_p = time_point::from_iso_string(date_str);
return time_point_sec{ time_p };
}
复制代码
3、to_string()
返回时间字符串(注:字符串格式为 %Y-%m-%dT%H:%M:%S,精度为秒)
std::string to_string() const {
return ((time_point)(*this)).to_string();
}
复制代码
4、sec_since_epoch()
返回当前实例的 Unix 纪元时间戳(精度:秒)
uint32_t sec_since_epoch()const { return utc_seconds; }
复制代码
5、测试用例
/*
测试time_point_sec类,由transaction_header.expiration获取当前交易的time_point_sec实例,并进行如下测试:
测试from_iso_string()验证时间转换精度
测试to_string()返回时间字符串
测试sec_since_epoch()返回时间戳
*/
ACTION tstime::gettime(){
transaction_header _trx_header;
eosio::time_point_sec trx_time_sec = _trx_header.expiration;
//测试from_iso_string()验证时间转换精度
auto trx_time_secmy01 = eosio::time_point_sec::from_iso_string("2023-03-31T01:31:15.000").sec_since_epoch();//返回时间戳1680226275(秒)
auto trx_time_secmy02 = eosio::time_point_sec::from_iso_string("2023-03-31T01:31:15.500").sec_since_epoch();//返回时间戳1680226275(秒)结果相同,时间字符串的解析精度为秒
//测试to_string()返回时间字符串
auto trx_time_str = trx_time_sec.to_string();
//测试sec_since_epoch()返回时间戳
auto trx_timestamp = trx_time_sec.sec_since_epoch(); // 返回当前交易时间戳16832743977(秒)
}
复制代码
-END-
评论