原文链接:醒者呆的博客园,https://www.cnblogs.com/Evsward/p/storage.html
谈到区块链的存储,我们很容易联想到它的链式存储结构,然而区块链从比特币发展到今日当红的EOS,技术形态已经演化了10年之久。目前的EOS的存储除了确认结构的链式存储以外,在状态存储方面有了很大的进步,尤其是引入了MongoDB plugin以后,可以将功能有限的状态库搭上大数据的班车。本文将全面介绍EOS的存储技术。
EOS 存储,Merkle Tree,mongodb,chainbase,源码学习,context_free_actions
EOS的区块数据结构如下:
field | explanation |
---|---|
timestamp | 时间戳 |
producer | 生产者 |
confirmed | 生产者确认数 |
previous | 链式结构前一个区块的id |
transaction_mroot | 交易默克尔树根 |
action_mroot | 动作默克尔树根 |
schedule_version | 生产者版本排序号 |
new_producers | 下一个生产者 |
header_extensions | 区块头扩展字段 |
producer_signature | 区块签名,由生产者签名 |
transactions | 块打包交易内容,是数组结构,可以多个 |
block_extensions | 区块扩展字段 |
id | 当前块id |
block_num | 当前块高度 |
ref_block_prefix | 引用区块的区块头 |
默克尔树的演化路线是 Hash => Hash Tree => Merkle Tree ,他们都是为解决数据一致性而存在的,具体的含义如下:
上面的区块数据结构中包含了两个与Merkle Tree相关的字段:
action_mroot是始终有值的,哪怕transaction_mroot是0,这是因为出块本身也是一个action动作onblock,这个动作调用的是system合约的onblock函数。TODO:源码分析
/**
* At the start of each block we notify the system contract with a transaction that passes in
* the block header of the prior block (which is currently our head block)
*/
signed_transaction get_on_block_transaction()
{
action on_block_act;
on_block_act.account = config::system_account_name;
on_block_act.name = N(onblock);
on_block_act.authorization = vector<permission_level>{{config::system_account_name, config::active_name}};
on_block_act.data = fc::raw::pack(self.head_block_header());
signed_transaction trx;
trx.actions.emplace_back(std::move(on_block_act));
trx.set_reference_block(self.head_block_id());
trx.expiration = self.pending_block_time() + fc::microseconds(999'999); // Round up to nearest second to avoid appearing expired
return trx;
}
通过对eosio.null账户的nouce动作,可以将无签名的数据打包进入context_free_action字段,结果区块信息如下:
evsward@evsward-TM1701:~/work/src/github.com/eos/tutorials/bios-boot-tutorial$ cleos --wallet-url http://127.0.0.1:6666 --url http://127.0.0.1:8000 get block 440
{
"timestamp": "2018-08-14T08:47:09.000",
"producer": "eosio",
"confirmed": 0,
"previous": "000001b760e4a6610d122c5aa5d855aa49e29f3052ac3e40b9e1ef78e0f1fd02",
"transaction_mroot": "32cb43abd7863f162f4d8f3ab9026623ea99d3f8261d2c8b4d8bf920ab97e3d1",
"action_mroot": "09afeaf40d6988a14e9e92817d2ccf4023b280075c99f13782a6535ccc58cbb0",
"schedule_version": 0,
"new_producers": null,
"header_extensions": [],
"producer_signature": "SIG_K1_K2eFDzbxCg3hmQzpzPuLYmiesrciPmTHdeNsQDyFgcHUMFeMC3PntXTqiup5VuNmyb7qmH18FBdMuNKsc7jgCm1TSPFbaj",
"transactions": [{
"status": "executed",
"cpu_usage_us": 290,
"net_usage_words": 16,
"trx": {
"id": "d74843749d1e255f13572b7a3b95af9ddd6df23d1d0ad19d88e1496091d4be2b",
"signatures": [
"SIG_K1_KVzwg3QRH6ZmempNsvAxpPQa42hF4tDpV5cqwqo7EY4oSU7NMrEFwG7gdSDCnUHHhmH1EwtVAmV1z9bqtTvvQNSXiSgaWG"
],
"compression": "none",
"packed_context_free_data": "",
"context_free_data": [],
"packed_trx": "8497725bb601973ea96f0000000100408c7a02ea3055000000000085269d000706686168616861010082c95865ea3055000000000000806b010082c95865ea305500000000a8ed3232080000000000d08cf200",
"transaction": {
"expiration": "2018-08-14T08:49:08",
"ref_block_num": 438,
"ref_block_prefix": 1873362583,
"max_net_usage_words": 0,
"max_cpu_usage_ms": 0,
"delay_sec": 0,
"context_free_actions": [{
"account": "eosio.null",
"name": "nonce",
"authorization": [],
"data": "06686168616861"
}
],
"actions": [{
"account": "eosiotesta1",
"name": "hi",
"authorization": [{
"actor": "eosiotesta1",
"permission": "active"
}
],
"data": {
"user": "yeah"
},
"hex_data": "0000000000d08cf2"
}
],
"transaction_extensions": []
}
}
}
],
"block_extensions": [],
"id": "000001b8d299602b289a9194bd698476c5d39c5ad88235460908e9d43d04edc8",
"block_num": 440,
"ref_block_prefix": 2492570152
}
正常的actions的内容是hi智能合约的调用,而context_free_action中包含了无签名的data数据,是已做数字摘要后的形态。源码中的操作:
//lets also push a context free action, the multi chain test will then also include a context free action
("context_free_actions", fc::variants({
fc::mutable_variant_object()
("account", name(config::null_account_name))
("name", "nonce")
("data", fc::raw::pack(v))
})
)
我们来设想一个场景:
A账户转账给B账户100个SYS,如何查看A账户的余额?
对于不知道以上动作何时发生的我们来讲,我们要如何做呢:
以上步骤很容易出错且繁琐,每一次的余额查询都要重复这些操作实在是毫无意义,因此StateDB就诞生了,这个库顾名思义就是用来存储状态数据的,如果有了StateDB,上面的场景的解决办法就是:
这里为大家提供一个测试方法,也是我的命令history:
cleos create key
cleos wallet import 5JA9oDotJHoKnjUV6NrAMx4g5gWTCVCRLybTnG1XVU3EKGZZeNY
cleos wallet import --private-key 5JA9oDotJHoKnjUV6NrAMx4g5gWTCVCRLybTnG1XVU3EKGZZeNY
cleos wallet keys
cleos wallet import --private-key 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
cleos wallet keys
cleos create account eosio usertesta1 EOS761KfjhYy3FSypZGG5hePrR8K2wzmw75JCDXgypKt2DLXZoZPW
cleos create account eosio eosio.token EOS761KfjhYy3FSypZGG5hePrR8K2wzmw75JCDXgypKt2DLXZoZPW
cleos set contract eosio.token work/src/github.com/eos/build/contracts/eosio.token/
cleos push action eosio.token create '["eosio","100000000.0000 SYS"]'
cleos push action eosio.token issue
cleos push action eosio.token issue '["usertesta1","10000000.0000 SYS"]' -p eosio.token
cleos push action eosio.token issue '["usertesta1","10000000.0000 SYS"]' -p eosio
cleos get currency balance eosio.token usertesta1
cleos get table eosio.token usertesta1 accounts
cleos get table eosio.token eosio accounts
可以看到当我想获得usertesta1账户的余额时,是通过查询StateDB的table来获取的,而不是最开始的那种扫块的笨方法。
{
"account": "eosiotesta1",
"name": "hi",
"authorization": [{
"actor": "eosiotesta1",
"permission": "active"
}],
"data": {
"user": "yeah"
},
"hex_data": "0000000000d08cf2"
}
这个例子中,我们调用了hello合约的hi函数,data传入的格式是hi函数中自定义的,所以在链式存储中,留给我们发挥的空间也即在此。
很多人搞不明白为什么区块链不可篡改,却在StateDB中好像可以修改还能删除?
其实不是这样的,链式存储的内容会将所有的动作action全部记录下来,是所有的过程数据,是流水帐,元数据,这些数据一旦上链是不可修改,不可删除的。而StateDB只是为了保存一个状态信息,这个状态信息的修改与删除并不影响区块链的不可篡改的特性。
目前StateDB的主流实现方式是将它放在内存中,而当有些人对StateDB的认识有偏差造成滥用的时候,会引发内存过载,因此一方面我们要非常清楚的理解StateDB的含义,一方面EOSIO帮助我们提供了一个mongodb-plugin插件来同步StateDB数据。
我们也可以使用ubuntu系统的服务模式。
曾经我们要定义一个系统启动时自启动服务的方式是在/etc/init.d 目录下写一个脚本来执行,现在在ubuntu的服务模式下,我们可以丢弃那种方式,服务模式的命令是service,而现在的ubuntu系统推崇使用的systemctl命令,他俩的使用方法的区别就在于参数的顺序。
sudo vi /etc/systemd/system/mongodb.service
[Unit]
Description=High-performance, schema-free document-oriented database
After=network.target
[Service]
User=mongodb
ExecStart=['mongod' command location] --quiet --config /etc/mongod.conf
[Install]
WantedBy=multi-user.target
systemctl list-unit-files
systemctl is-enabled mongodb
sudo systemctl enable mongodb
sudo systemctl start mongodb
sudo systemctl status mongodb
sudo systemctl stop mongodb
###调试模式
IDE选择CLion,EOS源码下载最新的,保证本地可以使用脚本编译通过,安装了相关依赖包,然后在CLion中导入EOS,CLion会自动识别CMakeList.txt文件生成makefile文件并make编译执行。编译时可能会遇到错误,一般来讲要么是环境依赖没有配置好,要么就是CMakeList.txt要有修改,例如mongodb-plugin导入时要在总开关配置上开启。
set(BUILD_MONGO_DB_PLUGIN "true")
全部编译成功以后,会自动识别出可以debug的target,与EOS中配备CMakeList.txt的模块一一对应。
上面我们介绍了MongoDB的安装方法,以及启动nodeos时的配置方法(除了上文提到的总开关,当然要在config.ini文件末尾设置上plugin = eosio::mongo_db_plugin,这部分内容演练多次,这里不再赘述。)链启动开始出块以后,会同步到mongodb中去(注意要预先启动mongod守护进程,可以理解为服务端),通过mongo命令接入可使用mongo命令查询数据,但这样很不方便。可以在CLion中安装mongo-plugin,配置好效果如下:
圆方圆学院汇集大批区块链名师,打造精品的区块链技术课程。 在各大平台都长期有优质免费公开课,欢迎报名收看。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。