社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  区块链

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

区块链技术学习 • 6 年前 • 551 次点击  

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

链接: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{{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获取文章目录

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/34994
 
551 次点击