以太坊区块链中的数据结构

区块链 同时被 2 个专栏收录
34 篇文章 1 订阅

本文重点介绍以太坊中区块链相关的基础概念,适用于入门级别的同学观看,对以太坊中的基础知识有一个大概的了解。
本文目的是使得大家看完之后对以太坊有一个直观的了解,因此对以太坊中的具体细节,不会深入阐述,以免新手们陷入冗余细节中而不能一览全貌。

以太坊

以太坊是一个基于交易的状态机,其区块链中的每个区块就对应一个状态,每产生一个区块,以太坊中的状态就会转换到下一个状态。通过状态转换使得运行以太坊中的所有节点保持数据的一致性。以太坊中的区块链分布式存在,但与传统的分布式却截然不同。


从创世块开始,不断产生的交易持续刷新系统当前状态,每当产生一个区块,系统状态就发生一次转换。

以太坊中的哈希

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

以太坊中的序列化方法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)

$encode(data) = SHA3(RLP(data))$

区块的组成

以太坊中,区块主要由三部分组成:区块头(Block Header),叔块(Uncle),交易列表(tx_List)。这三个部分也是矿工在网络中发布的区块的内容,如下图所示。

区块头由15个字段组成。

叔块其实就是孤块,因以太坊出块速度很快平均十几秒就会打包生成一个块,所以矿工挖矿的竞争性很高,可能同时产出几个都合法的区块,以太坊为了一些安全性起见,允许竞争块也挂在到主链上,同时给与挖出这些孤块的矿工们少许奖励增加工作的公平性。具体参见以太坊中的Ghost协议
交易列表,存储的是本区块中所有的交易内容。

区块头结构

以太坊中每个区块的结构和比特币中每个区块的结构却不同,以太坊中每个区块的结构比比特币种区块的结构更加复杂,太坊中区块的区块头(Block Header)结构定义如下图所示。

区块头中每个字段的意义如下:

名称类型意义
parentHashcommon.Hash父区块的哈希值
UncleHashcommon.Hash叔父区块列表的哈希值
Coinbasecommon.Address打包该区块的矿工的地址,用于接收矿工费
Rootcommon.Hash状态树的根哈希值
TxHashcommon.Hash交易树的根哈希值
ReceiptHashcommon.Hash收据树的根哈希值
BloomBloom交易收据日志组成的Bloom过滤器
Difficulty*Big.Int本区块的难度
Number*Big.Int本区块块号,区块号从0号开始算起
GasLimituint64本区块中所有交易消耗的Gas上限,这个数值不等于所有交易的中Gas limit字段的和
GasUseduint64本区块中所有交易使用的Gas之和
Time*big.Int区块产生的unix时间戳,一般是打包区块的时间,这个字段不是出块的时间戳
Extra[]byte区块的附加数据
MixDigestcommon.Hash哈希值,与Nonce的组合用于工作量计算
NonceBlockNonce区块产生时的随机值

和比特币中的区块链一样,以太坊中的区块链仍然使用了parentHash连接前一个区块,通过这个字段,所有的区块最终都能回溯到创始块。
值得一提的是,区块的时间戳并不是出块的时间,矿工在挖矿的时候区块的时间戳一般是确定的,所以这个区块的时间戳,是矿工开始挖这个区块的时间。
Gas Limit是用于限制区块中的交易个数用的。具体由每个矿工自己设置,有些矿工觉得本区块的Gas Limit太大,可以在挖下一个区块时间Gas Limit下调 1 1024 \frac1{1024} 10241,觉得Gas Limit太小的时候,就会上调 1 1024 \frac1{1024} 10241。整个区块链中区块的大小,就是所有矿工微调的平均值。以太坊中正是通过Gas Limit来限制区块的大小。

区块头中比较重要的三个字段是Root、TxHash和ReceiptHash三个哈希值。其中Root表示以太坊网络中全部有过转账交易的账户形成的Merkle partial Tree(MPT)的根哈希值。为什么是全部有个转账交易的账户呢,因为我们可以随便创建一个以太坊账户,但是没有发生交易的话,以太坊节点也不会知道这个账户的存在。TxHash是本区块中所有交易形成的merkle tree的根哈希值,ReceiptHash是本区块中所有收据信息构成的merkle tree 的哈希值。这其中涉及到的Merkle tree的相关概念可以参考比特币区块链中的数据结构,里面有merkle tree数据结构的介绍。接下来重点介绍Merkle Partial Tree。

Merkle Patricia Tree(默克尔-帕特里夏树)

比特币是基于交易的分布式账本,而以太坊是基于账户的分布式账本,相比于比特币,以太坊设计的更加复杂。以太坊中会对所有有过转账交易的账户都会建立一个用户状态树,每个运行以太坊客户端的节点都会在本地维护一个用户状态树,当有新的区块打包到区块链上之后,所有的以太坊节点(挖出区块的节点除外)的就会执行最新区块中的交易,对本地用户的状态树进行维护,得到新的用户状态树。用户状态树的信息并不保存在区块中,区块中仅仅保存了最新的状态树组成的根哈希值。

关于MPT的介绍,网上资料众多,但我认为比较容易理解的文章,可参见以太坊MPT原理,和这篇Understand ethereum’s trie,这两篇是网上对于MPT的解释比较到位的文章。
但是需要强调的是,以太坊中状态树之间的指针式哈希指针,这样从底层的value建立哈希指针,再对其父节点建立哈希指针,以此类推,直到最终的状态树merkle root。
每次发布新的区块链时,每个存有状态树的节点就会根据区块中的交易更新对应的账户树中的内容,每个节点根据区块上的交易独立的修改状态树,最后生成状态树的根哈希值,随后用本地生成的状态树哈希值和新发布区块中的状态树的Root字段进行比较,如果相同,表示账户状态和发布区块的节点的账户状态保持了一致。以太坊中每个节点独立的运行每个区块上的交易,随后验证区块,一个合法的区块,所有节点运行过后其账户状态就能保持一致。这就是以太坊中每个节点独立运行却又能维持区块链数据的一致性的原因。

状态树的变化可以用下图所示。每个状态发生改变时,都会新建一个节点,但是原有的状态树仍然会保存,保存原有状态的原因是因为以太坊出块时间是十几秒,在十几秒钟很容易出现多个合法的区块,就会出现分叉,分叉的区块最终合并之后,有一些分叉链上的节点的状态就需要回退,保存了历史记录就是方便随时对状态的回退。

区块中的交易

以下代码部分来自于以太坊客户端的c++源代码(ethcore\TransactionBase.h)

class TransactionBase
{
protected:
	Type m_type = NullTransaction;  // 交易类型,表明该交易是合约创建交易还是一般的message-call 交易
	u256 m_nonce;                   // 消息发送方的交易次数			
	u256 m_value;                   // 要交易的以太币数量,若是创建合约的交易,称为'endowment'					
	Address m_receiveAddress;       // 交易接收方地址。如果要创建合约,则将地址写为0x00
	u256 m_gasPrice;                // 本次交易的gas的单价					
	u256 m_gas;	                    // 本次交易最多消耗的gas数量						
	bytes m_data;                   // 与交易相关的数据。或者是创建合约的初始化的一些参数。					
	boost::optional<SignatureStruct> m_vrs;	// 交易的签名,对交易发起方进行编码
	int m_chainId = -4;	// EIP155计算的哈希值,见https://github.com/ethereum/EIPs/issues/155
	mutable h256 m_hashWith;        // < Cached hash of transaction with signature.
	mutable Address m_sender;	    // < Cached sender, determined from signature.	
	// 交易类型
		enum Type
		{
			NullTransaction,		//< 空交易.
			ContractCreation,		//< 创建合约的交易,直接忽略地址。
			MessageCall				//< message-call交易
		};
	};	

可以看出一个交易中明确的给出了发送地址,发送以太币数值,交易费等信息,而以交易发起方的地址信息,则以签名的形式给出。和比特币类似相同,以太坊中对每个区块中的交易,经过哈希后生成Merkle Patricia Root,这对应了区块头中的TxHash字段。
###区块中的收据
以太坊中的每一笔交易都有对应的收据,收据树的信息是为了方便一些相关账户的查询。与前面的状态树不同的是,交易树和收据树的形成都是只关于本区块中所有的交易。交易树和收据树其中一个作用是,可以用来做Merkle proof,以证明区块中某笔交易的存在。除此以外,以太坊中如果需要查找过去某段日期内和某个智能合约的相关交易,如果没有收据树,只能遍历过去所有的区块以查找符合特定需求的操作,而如果查找的账户不存在,则需要遍历到创始块,这样的代价实在太大,于是,以太坊中引入了bloom Filter。
Bloom Filter有什么用呢?以太坊中的每一笔交易执行完毕就会生成一个收据,收据里面包含了一个Bloom Filter的日志,记录了交易类型和地址等信息。区块的块头中也有一个Bloom Filter,里面是所有交易生成的收据中的Bloom Filter的并集。当我们需要查找和某个账户相关的交易时,我们可以利用区块头中的Bloom Filter进行查找,查找该区块中是否存在关于某个账户的交易,如果块头的Bloom Filter中没有找到,这说明该区块不存在和某个账户相关的交易;如果查找存在,我们还需要进一步在交易中寻找是否存在相关交易。
区块中的收据信息的相关代码定义如下:

using LogEntries = std::vector<LogEntry>;
class TransactionReceipt{
	private:
	boost::variant<uint8_t,h256> m_statusCodeOrStateRoot; // 状态码或者状态树根
	u256 m_gasUsed;                                       // 消耗的汽油,u256是256位的无符号整数
	LogBloom m_bloom;                                     // bloom日志
	LogEntries m_log;                                     // 日志
	};

上述中的 LogEntries是LogEntry的vector集合,LogEntry的数据结构如下所示。是一个结构体,里面包含一个地址、topics的256位的哈希数的vector,以及字节数组组成的data。下述代码中的using语句并不存在于源代码中,我添加到struct中方便理解每个类型的定义。每个LogEntry中包括了一个收款人的地址和其他相关信息,而在收据信息中包括了日志、bloom过滤器和这笔交易消耗的汽油。

struct LogEntry
{
	using bytes = std::vector<byte>;// byte的vector,源代码struct中没有这句。
	using h256s = std::vector<h256>;// 256位哈希值的vector,源代码struct中没有这句。
	using Address = h160;           // 160位的哈希地址,源代码struct中没有这句。
	Address address;
	h256s topics;
	bytes data;
};

至此,以太坊中一些重要的数据结构已经介绍完毕,通过这些可以对以太坊的区块链有一个比较大致的了解。
参考阅读:
以太坊的数据结构
以太坊MPT原理

  • 2
    点赞
  • 0
    评论
  • 16
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值