本文档从区块时间基本概念出发,介绍了中移链的区块时间接口和应用方向。适用于EOS智能合约的高级开发人员,熟悉如何获取当前区块时间、下一个区块的区块时间、时间戳的转换等。
01
概述
(一)时间戳
时间戳是标识特定事件何时发生的字符序列或编码信息。如今,该术语的用法已经扩展到指附加在数字信息上的数字日期和时间信息。例如,计算机文件包含时间戳,用于提示文件最后一次修改的时间。然而随着数字化文档的诞生,电子数据具有脆弱性、易变性、隐蔽性、载体多样性等特点,容易被复制、删除、篡改且难以被发现。因此,电子数据在实际的司法认定过程中,很难准确坚定其生成的时间以及内容的真实性、完整性。
(二)区块链时间戳
区块链时间戳系统实际就是在P2P网络上通过节点间的共识算法实现的分布式时间戳服务。它是利用时间戳实现在时间上有序的、由一个个区块组成的一根链条。每一个新区块生成时,都会被打上时间戳,最终依照区块生成时间的先后顺序相连成区块链,每个独立节点又通过P2P网络建立联系,这样就为信息数据的记录形成了一个去中心化的分布式时间戳服务系统。
(三)区块链时间戳特点
因区块链拥有以下特点,使得时间戳可以完全信任:(1)不可销毁/修改;(2)Block具有天然时间特性,时间戳是Block meta字段之一;(3)Block可以存储交易信息,交易是可以“写入”的数据;时间戳使得更改一条记录的困难程度按时间的指数倍增加,越老的记录越难修改。
02
环境依赖
eosio_2.1.0-1:
https://github.com/eosio/eos/releases/download/v2.1.0/eosio_2.1.0-1-ubuntu-18.04_amd64.deb
eosio.cdt v1.8.x:
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
#include
auto cur_timestamp = current_block_time();
2、current_time_point()
获取当前块的Unix纪元时间戳(time_point实例,以微秒为单位)。
调用方式
#include
#include
auto cur_timepoint = current_time_point();
3、expiration()
获取当前交易的Unix纪元时间戳(uint32_t,以秒为单位)。
调用方式
#include
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::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
当前实例的过期时间(单位微秒)
源码描述
microseconds 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( tp.time_since_epoch() );
return time_point{ microseconds{ static_cast(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-