以太坊基本数据结构分析

声明:此为使用网上多处资料整理而成,由于很多地方内容相同,已经分不清哪里是原创

一. 以太坊的区块结构

在这里插入图片描述
从上图可以看到,区块由两部分组成,分别是区块头(header)和区块体(body)两部分。

1. 区块头(header)

区块头存储了区块的元信息,用来对区块内容进行一些标识,校验,说明等。区块头里字段分为两部分区块头和区块体。

通用字段

    ParentHash: 父区块的哈希值
	Root:全局状态MPT树的根哈希,世界状态的哈希,这个全局状态树包含了以太坊网络中每一个账户的一组键值对,stateDB的RLP编码后的哈希值
	TxHash(transaction root hash):交易MPT树的根哈希,由本区块所有交易的交易哈希算出
	ReceptHash:收据MPT树的哈希
	Time:区块产生出来的Unix时间戳
	Number:区块号
	Bloom:布隆过滤器,快速定位日志是否在这个区块中。

公链场景

Coinbase:挖出这个块的矿工地址,因为挖出块所奖励的ETH就会发放到这个地址。
Difficulty:当前工作量证明(Pow)算法的复杂度。
GasLimit: 每个区块Gas的消耗上线。
GasUsed:当前区块所有交易使用的Gas之和。
MixDigest: 挖矿得到的Pow算法证明的摘要,也就是挖矿的工作量证明。
nonce:挖矿找到的满足条件的值。
UncleHash:叔块是和以太坊的共识算法相关。
一般而言一个类以太坊的联盟链是需要上面介绍的通用字段的,但是也不绝对,还可能与选择的共识算法,隐私保护策略,设计偏好有关。

每个块都有一个「header」,它存储三个不同Merkle树结构根节点的哈希,包括:

  • 状态树
  • 交易树
  • 收据树

它允许轻客户端轻松地进行并核实以下类型的查询答案:

这笔交易被包含在特定的区块中了么?      --交易树(transaction tree)
告诉我这个地址在过去30天中,发出X类型事件的所有实例(例如,一个众筹合约完成了它的目标) --收据树(receipt tree)
目前我的账户余额是多少?            --状态树(state tree)
这个账户是否存在?                 --状态树(state tree)
假装在这个合约中运行这笔交易,它的输出会是什么?   --状态树(state tree)

第一种是由交易树(transaction tree)来处理的;第三和第四种则是由状态树(state tree)负责处理,第二种则由收据树(receipt tree)处理。计算前四个查询任务是相当简单的。服务器简单地找到对象,获取梅克尔分支,并通过分支来回复轻客户端。

第五种查询任务同样也是由状态树处理,但它的计算方式会比较复杂。这里,我们需要构建下我们称之为梅克尔状态转变的证明(Merkle state transition proof)。从本质上来讲,这样的证明也就是在说“如果你在根S的状态树上运行交易T,其结果状态树将是根为S’,log为L,输出为O” (“输出”作为存在于以太坊的一种概念,因为每一笔交易都是一个函数调用,它在理论上并不是必要的)。

为了推断这个证明,服务器在本地创建了一个假的区块,将状态设为 S,并假装是一个轻客户端,同时请求这笔交易。也就是说,如果请求这笔交易的过程,需要客户端确定一个账户的余额,这个轻客户端会发出一个余额疑问。如果这个轻客户端需要检查存储在一个特定合约的特定项目,该轻客户端会对此发出针对查询。服务器会正确地“回应”它所有的查询,但服务器也会跟踪它所有发回的数据。然后,服务器会把综合数据发送给客户端。客户端会进行相同的步骤,但会使用它的数据库所提供的证明。如果它的结果和服务器要求的是相同的,那客户端就接受证明。

1)状态树

全局状态树包含了以太坊网络中每一个账户的一组键值对,每次生成一个新的区块,以太坊状态发生改变后并不会去修改原来的MPT树,而是会新建一些分支,如下图所示;

在这里插入图片描述

全局状态树的Key是一个 160 位的标识符(以太坊账户的地址),全局状态树中的 “值” 是通过编码以太坊账户中的如下细节来得到的(使用RLP的方法):

  • nonce 值
  • 余额
  • 存储前缀树根节点哈希
  • 代码哈希

    存储树是智能合约数据存储的位置,每一个以太坊账户都有自己的存储树
    在这里插入图片描述

优势

  • 当一个账户的余额发生改变后,对应路径的哈希也发生了变化,然后自底而上的更新对应路径上的哈希值,直至Satet Root,这样可以计算最少的哈希次数。
  • 以太坊中的全节点维护的是增量的MPT状态树,因为每次一个区块对世界状态的修改都只是很小的一部分,增量修改既有利于区块回滚,又可以节约开销。
  • 在以太坊中区块临时分叉很普遍,但是由于以太坊智能合约的复杂性,如果不记录原始状态,很难根据合约代码回滚状态。

2)收据树

以太坊在智能合约执行时会产生一个交易回执(Receipt)记录了此笔交易的执行结果,交易信息和区块信息。

在这里插入图片描述
当查询轻节点查询通过布隆过滤器找到交易后,为了避免误识,还会再次查询回执来避免误识。

3)交易树

交易树的作用是提供了交易的默克尔证明,证明某个交易被打包到某个区块里,轻节点不用存储区块体仅根据提供的默克尔证明就可以快速判断交易是否已经被打包。

4)三颗树的差异

交易树和收据树只依赖当前的区块,而状态树是把链上所有状态都包含进去,交易树和收据树是独立的,状态树会共享树的节点。

为什么状态树要包含所有链上所有的状态呢?

举个例子,当一笔转账操作的发起时,需要判断发起账户是否有足够的ETH来完成这笔转账,这个时候要通过查找状态树查看对应账户的状态,但是如果为了节约空间,只保存了当前区块账户的状态,就需要逐块查找,非常影响性能,甚至这个转账交易的发起者都不存在,是一个恶意操作。

2. 区块体(body)

区块体包括这个区块打包的所有交易,在一些链的设计中,并不像以太坊区分header和body,而是整合在一起。

3. 区块存储

以太坊在存储区块的时候,区块头和区块体其实是分开存储的,其实也很容易理解,分开存储可以提供更多的灵活性,比如不用保存全部区块数据的轻节点。

区块头存储,以太坊通过如下方式将区块头转换成键值对存储在LevelDB中;

headerPrefix + num + hash  -> rlp(header)
Tips: num是以大端序的形式转换成bytes的,其中headerPrefix的值是 []byte("h")

区块体存储

bodyPrefix + num + hash -> rlp(block)
Tips: num是以大端序的形式转换成bytes的,其中bodyPrefix的值是[]byte("b")

4. 潜在问题

假设在一个联盟链的场景下,采用了BFT类的算法,有一个重量级的业务跑在上面,日积月累产生了大量的数据,是否会出现LevelDB的读写性能大幅下降拖慢系统的响应速度?单机存储无法满足需要?存储了大量的不会使用的历史数据?

在联盟链的场景下,由于共识速度的提升,导致出块速度也大幅提升,原本在公链场景下不存在的区块写入瓶颈,现在反而成了拖慢系统运行速度的重要因素了。

观察一下区块数据的存储就可以发现下面的这些特点;

  • 区块数据只会增加;
  • 无需对历史区块进行修改;
  • 无需对区块数据进行复杂操作,比如聚合,运算等;

归纳一下就是顺序写,随机读和迭代(Iterator),针对这些特点Hyperledger Fabric设计了基于文件的存储方式,在Fabric中区块数据是以一个个文件的形式存在。

chains
  |----mychannel
  |----|----blockfile_000000
index
  |----000001.log
  |----CURRENT
  |----LOCK
  |----LOG
  |----MANIFEST-000000

其中blockfile_000000是区块数据,index则是索引游标等元信息,这种方式速度很快,方便做数据归档,也可以避免像LevelDB等数据库数据越写越慢的问题,主流联盟链都是采用类似的方案。

以太坊的帐户

以太坊的全球「共享状态」是由许多账户组成的,它们能够通过一个消息传递框架相互通信。每个帐户都有一个与它关联的状态和一个20字节的地址。以太坊的地址是一个160位比特的标识符,用于识别帐户。

以太坊有两种账户类型:

  • 外部帐户由私人密钥控制,没有与之相关的代码。
  • 合约账户由其合约代码控制,并具有与其相关的代码。

以太坊中的哈希

无论是比特币还是以太坊中,都采用了SHA(Security Hash Algorithm)哈希函数进行加密,在比特币中采用了SHA256的哈希函数,而在以太坊中,使用SHA3函数(Keccak函数)。它们的区别在于,SHA256属于SHA-2,即第2代哈希函数,而SHA3属于第3代哈希函数,第1代哈希函数已经被破解,已经不再适用于加密了。包括我们之前使用的MD5加密函数,也已经被发现可以制造碰撞,已经废弃了。2015年8月5日,美国标准技术协会(NIST)正式发布了SHA3,以其作为最新的一代标准加密函数。值得说明的是,比特币中的SHA256目前也没有被发现可以人为制造碰撞的方法。经过SHA256加密后可以得到长度为256bits的哈希值,比特币中一个用户的账户地址,就是将其公钥输入到SHA256算法中得到256bits的输出得到。而以太坊中,经过SHA3加密后得到160bits的输出。

以太坊中的序列化方法RLP

RLP(Recursive Length Prefix)可以将任意的数据编码成二进制byte的数组,即[]byte的形式。同时已知数据的RLP编码结果,可以求出其原来的形式。RLP在以太坊中作用主要有如下几个:

1.对结构体数据进行编码
2.将特殊的数据类型(string,floats等)编码为更高级的协议

RLP是以太坊中对数据进行编码的主要手段。以太坊要用到SHA3函数的地方,首先对该数据进行RLP编码,随后对RLP编码后的数据进行SHA3运算。因此以太坊中的SHA3计算是下列方式。更加详细的内容可以参见[RLP]。(https://github.com/ethereum/wiki/wiki/RLP)

e n c o d e ( d a t a ) = S H A 3 ( R L P ( d a t a ) ) encode(data) = SHA3(RLP(data)) encode(data)=SHA3(RLP(data))

帐户状态

无论帐户是哪种类型,帐户状态都由以下四个部分组成。

  • nonce:如果帐户是一个外部帐户,这个数字代表从帐户地址发送的交易数量。如果帐户是一个合约帐户,nonce是帐户创建的合约数量。
  • balance:这个地址拥有的Wei(以太坊货币单位)数量,每个以太币有1e+18 Wei。
  • storageRoot :一个Merkle Patricia树根节点的哈希,它对帐户的存储内容的哈希值进行编码,并默认为空。
  • codeHash:EVM(以太坊虚拟机)的哈希值代码。 对于合约帐户,这是一个被哈希后并存储为codeHash的代码。对于外部帐户,codeHash字段是空字符串的哈希。
    在这里插入图片描述
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值