区块链 + 大数据:EOS存储技术

TokenClub资讯 761 2019-06-20 12:27
本文来自网络抓取,如果侵权请联系删除

来自:博客园,作者:一面千人

链接:https://www.cnblogs.com/Evsward/p/storage.html


谈到区块链的存储,我们很容易联想到它的链式存储结构,然而区块链从比特币发展到今日当红的EOS,技术形态已经演化了10年之久。目前的EOS的存储除了确认结构的链式存储以外,在状态存储方面有了很大的进步,尤其是引入了MongoDB plugin以后,可以将功能有限的状态库搭上大数据的班车。


本文将全面介绍EOS的存储技术:

EOS 存储,Merkle Tree,mongodb,chainbase,源码学习,context_free_actions


EOS的链式存储结构


EOS的区块数据结构如下:



Merkle Tree


默克尔树的演化路线是 Hash => Hash Tree => Merkle Tree ,他们都是为解决数据一致性而存在的,具体的含义如下:


Hash 是我们都熟知的技术了,它可以为一个文件或其他数据生成一个Hash值,我们在下载一个文件时,通常会在下载页面看到这个文件的Hash值以及该Hash值的算法,下载完毕以后,我们可以在本地对整个文件进行同样的Hash算法得到Hash值,然后与网页上的Hash值进行对比,如果相同,则说明文件完整,是网页上的源文件,如果不匹配则说明文件损坏,被修改或者不完整。


仍旧是文件完整性的校验需求,当这个文件特别大的时候,对这个文件进行Hash算法是能耗巨大的,所以可以将文件切割成很多的小块,每一个小块都有一个Hash,然后将所有小块的Hash值拼在一起再次进行Hash算法得到的就是Root Hash。这样一来,我们在下载大文件的时候,会先下载一个包含Root Hash的Hash list,通过校验Root Hash可以确定Hash list的正确性,确定Hash list正确以后,再逐个下载小块文件并逐一验证Hash,当发现某个小块Hash不匹配的时候,就可以单独重新下载该小块即可,而不必重新下载全部。Hash list的结构实际上是一个Root Hash 为根,小块Hashs为叶子节点的树高为2的Hash Tree。


Merkle Tree实际上是对Hash List的优化,它极大的提高了性能。它的结构是一个二叉树(也可以是多叉树,性能优化的关键点是它的高度是大于等于2的),每个节点最多只有两个子节点,只有叶节点是根据小块文件做的Hash,每两个相邻的叶节点的父节点是由这两个Hash做的父Hash,如果叶节点的总数是单数,则会剩余一个,逐级而上,最终会有一个的根节点,这个根节点就是Merkle Root。这样以来,我们在下载大文件的时候,会首先下载一个Merkle Tree,从最左下叶节点进行校验,逐级而上,将整个Merkle Tree校验完毕。这里面不同于上面Hash Tree的是,只要最左下相邻的两个叶节点的Hash值与他们的父节点的Hash通过了匹配,则可以立即开始下载这两个叶节点对应的文件块,并行地,再校验其他叶节点,这就提高了性能,不必校验完整的Merkle Tree之后再下载文件。


Merkle Tree 与 区块链


上面的区块数据结构中包含了两个与Merkle Tree相关的字段:


transaction_mroot,一个区块中的transactions字段可以包含多笔交易,区块中的transaction_mroot是所有该区块内打包的交易的Merkle Root,可以用来校验其中的每笔交易的正确性。如果该区块中不包含任何交易,则该字段的值为0000000000000000000000000000000000000000000000000000000000000000。节点同步数据的时候,会先将交易的Merkle Tree下载并通过Merkle Root来校验,而不是将所有的交易主体全部下载下来,这样可以节省轻节点的数据量。


action_mroot,创建一个基于所有分发的action的根,在区块内接收交易时进行评估。用在轻客户端的校验,功能同上。


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;
}


context_free_actions


通过对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))
区块链;区块链;区块链;区块链;})
区块链;);


EOS的StateDB


我们来设想一个场景:


A账户转账给B账户100个SYS,如何查看A账户的余额?


对于不知道以上动作何时发生的我们来讲,我们要如何做呢:


1、首先是从头扫描区块内的交易,交易内的action,直到找到A账户被创建的action所对应的区块号。

2、从这个区块号开始继续扫描,要将所有A账户的转账,包括收入和支出的所有action记录下来并统计。

3、算出A的当前余额。


以上步骤很容易出错且繁琐,每一次的余额查询都要重复这些操作实在是毫无意义,因此StateDB就诞生了,这个库顾名思义就是用来存储状态数据的,如果有了StateDB,上面的场景的解决办法就是:


从A账户被第一次收入SYS开始,为A账户在StateDB中建立一个table,存储A账户的余额,每当A账户发生转账的action,都会同步更新StateDB中相关table中A账户的余额的值,当我们需要知道A账户的余额时,我们可以直接查找这个余额state即可。


测试用例


这里为大家提供一个测试方法,也是我的命令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来获取的,而不是最开始的那种扫块的笨方法。


链式存储和StateDB存储的区别


链式存储,存储的是固定结构的数据:Block=> Block Header/ transactions=>actions,一个action的结构例子:


{
区块链;区块链;区块链;区块链;"account":区块链;"eosiotesta1",
区块链;区块链;区块链;区块链;"name":区块链;"hi",
区块链;区块链;区块链;区块链;"authorization":区块链;[{
区块链;区块链;区块链;区块链;区块链;区块链;"actor":区块链;"eosiotesta1",
区块链;区块链;区块链;区块链;区块链;区块链;"permission":区块链;"active"
区块链;区块链;区块链;区块链;}],
区块链;区块链;区块链;区块链;"data":区块链;{
区块链;区块链;区块链;区块链;"user":区块链;"yeah"
区块链;区块链;区块链;区块链;},
区块链;区块链;区块链;区块链;"hex_data":区块链;"0000000000d08cf2"
}


这个例子中,我们调用了hello合约的hi函数,data传入的格式是hi函数中自定义的,所以在链式存储中,留给我们发挥的空间也即在此。


StateDB,存储的是一个最终要记录的状态,这个状态数据必须是有意义的,是有人关心的,无关紧要的数据请不要放在StateDB中去,所以StateDB是可以增删改查的,就像一个普通数据库那样,在合约中通过multi_index来操作,具体请参照文章EOS技术研究:合约与数据库交互(文章链接:https://www.cnblogs.com/Evsward/p/multi_index.html)


很多人搞不明白为什么区块链不可篡改,却在StateDB中好像可以修改还能删除?


其实不是这样的,链式存储的内容会将所有的动作action全部记录下来,是所有的过程数据,是流水帐,元数据,这些数据一旦上链是不可修改,不可删除的。而StateDB只是为了保存一个状态信息,这个状态信息的修改与删除并不影响区块链的不可篡改的特性。


目前StateDB的主流实现方式是将它放在内存中,而当有些人对StateDB的认识有偏差造成滥用的时候,会引发内存过载,因此一方面我们要非常清楚的理解StateDB的含义,一方面EOSIO帮助我们提供了一个mongodb-plugin插件来同步StateDB数据。


mongodb


安装


下载tgz安装包

解压安装到/usr/local/bin(或者其他某路径)

sudo mkdir /data/db


普通模式


sudo mongod

mongo


服务模式


我们也可以使用ubuntu系统的服务模式。


曾经我们要定义一个系统启动时自启动服务的方式是在/etc/init.d 目录下写一个脚本来执行,现在在ubuntu的服务模式下,我们可以丢弃那种方式,服务模式的命令是service,而现在的ubuntu系统推崇使用的systemctl命令,他俩的使用方法的区别就在于参数的顺序。


定义一个配置文件mongod.conf

定义一个服务文件,放置在/etc/systemd/system/


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

查询mongodb服务的激活状态

systemctl is-enabled mongodb

激活系统自启动服务

sudo systemctl enable mongodb

启动mongodb服务

sudo systemctl start mongodb

查询mongodb服务状态

sudo systemctl status mongodb

停止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的模块一一对应。


安装Mongo Explorer插件


上面我们介绍了MongoDB的安装方法,以及启动nodeos时的配置方法(除了上文提到的总开关,当然要在config.ini文件末尾设置上plugin = eosio::mongo_db_plugin,这部分内容演练多次,这里不再赘述。)链启动开始出块以后,会同步到mongodb中去(注意要预先启动mongod守护进程,可以理解为服务端),通过mongo命令接入可使用mongo命令查询数据,但这样很不方便。可以在CLion中安装mongo-plugin,配置好效果如下:




●编号218,输入编号直达本文

●输入m获取文章目录



<<免责声明:本文仅代表作者本人观点,并不代表小牛币读之观点,小牛币读的情报对作者观点不承担任何担保责任。>> 行情,快讯,资讯,您只需一个公众号
小牛币读公众号
长按识别二微码关注公众号
文章评论