解释以太坊状态试图加深对以太坊区块链的了解。 介绍 这篇文章解释了以太坊状态树。复仇通常被称为“世态町ñ e”和使用原始数据存储到记录状态（账户）和交易。由于 state trie 是 Ethereum 的核心数据库，因此了解它以加深您对 Ethereum 的了解非常重要。我构建的内容是为了让你在逻辑上一步一步地理解。当我学习它时，很难深入理解，因为状态树有多种类型，并且每个状态树都彼此密切相关。我希望这篇文章可以帮助您轻松深入地了解 state try。本文按顺序涵盖以下主题。 Merkle Patricia Trie World State Trie Transaction Trie Receipt Trie Account Storage Trie Merkle Patricia Trie（基数树/帕特里夏树/前缀树） Trie，也称为Radix Trie、Patricia Trie或Prefix Tree，是一种查找公共前缀最快、实现简单、占用内存小的数据结构。由于以太坊使用 Merkle Tree 将哈希高效地存储在块中，因此使用 Trie 作为数据存储的核心数据结构。以太坊使用“Modified Merkel Patricia Trie”，它是由 Merkle Tree、Patricia Tree(Trie) 和一些改进发明的。修改后的 Merkle Patricia Trie 作为以太坊尝试接收树、世界状态树、账户存储树和交易树中的主要数据结构。 上图显示了 Merkel Patricia Trie 的结构。它主要由三种类型的节点组成：扩展节点、分支节点和叶节点。每个节点由其内容的 sha3 散列值决定，并将散列用作键。Go-ethereum 使用 levelDB，parity 使用 RocksDB 存储状态。如果您想了解更深入的内容，请参阅“修改后的 Merkle Patricia Trie — 以太坊如何保存状态”。 状态树结构 在开始解释每个状态树之前，让我解释一下以太坊状态树的整个架构。如前所述，状态树有四种类型：世界状态树、交易树、交易收据树和账户存储树。每个状态树都是用 Merkle Patricia Trie 构建的，只有根节点（状态树的顶部节点）存储在块中以备用存储。您可以在下图中看到整个架构。 如您所见，三个主要的状态尝试：世界状态树、交易树和接收树被存储在块中。并且，账户存储树（account storage contents trie）在世界状态树中构造叶节点。 世界状态树（State Trie，全局状态树） 世界状态树是地址和帐户状态之间的映射。它可以被视为一个全局状态，通过事务执行不断更新。以太坊网络是一个分散的计算机，状态树被认为是硬盘驱动器。所有关于账户的信息都存储在世界状态树中，您可以通过查询来检索信息。世界状态树与账户存储树关系密切，因为它有“storageRoot”字段，指向账户存储树中的根节点。 帐户存储树 帐户存储树是存储与帐户关联的数据的地方。这仅与合约账户相关，所有智能合约数据都作为 32 字节整数之间的映射保存在账户存储树中。 并且，帐户状态存储有关帐户的信息，例如帐户有多少以及从帐户发送了多少交易。它有四个字段：nonce、balance、storageRoot 和 codeHash。它是世界状态树中的叶节点。 事务树 交易树记录以太坊中的交易。交易在改变状态方面起着核心作用，因为以太坊是基于交易的“状态”机器。一旦交易记录在一个区块中，就不能永久更改以证明账户余额（世界状态）。由于 Transaction Trie 是使用 Modified Merkel Patricia Trie 构建的，因此唯一的根节点存储在块中。下面的灰色框描述了交易数据字段。如果您想了解更多详细信息，请参阅以太坊交易结构说明。 nonce：交易 nonce 是从给定地址发送的交易序列号。 Gas Price：您愿意支付的价格 Gas Limit：Gas Limit 是发送方愿意为交易支付的 ETH 数量的限制 Recipient：收件人是以太坊地址的目的地。 Value：值字段表示从发送者到接收者的以太币/wei 的数量。 Data：数据字段用于合同相关活动，例如合同的部署或执行。 v,r,s：该字段是原始 EOA 的 ECDSA 数字签名的组成部分。 交易收据树（Receipt Trie） 交易收据 Trie 记录交易的收据（结果）。收据是交易成功执行的结果。收据包括交易的哈希值、区块号、使用的gas 数量和合约地址等。这是交易收据的字段。 blockHash: String, 32 Bytes - 此交易所在区块的哈希值。 blockNumber: Number - 此交易所在的区块号。 transactionHash: String, 32 Bytes - 交易的哈希值。 transactionIndex: Number - 区块中交易索引位置的整数。 from: String, 20 Bytes - 发件人的地址。 to: String, 20 Bytes - 接收者的地址。如果是合约创建交易，则为 null。 cumulativeGasUsed: Number - 在区块中执行此交易时使用的总燃料量。 gasUsed: Number - 仅此特定交易使用的 gas 量。 contractAddress: String - 20 Bytes - 创建的合约地址，如果交易是合约创建，否则为 null。 logs：数组 - 此事务生成的日志对象数组。 status : String - '0x0' 表示交易失败，'0x1' 表示交易成功。 引用：https : //ethereum.stackexchange.com/questions/6531/structure-of-a-transaction-receipt 结论 文章解释了以太坊的主要状态尝试：Merkle Patricia Trie、世界状态Trie、交易Trie、收据Trie和账户存储Trie。由于以太坊是一个世界的“状态机”，它具有原始的机制来记录和管理状态与特里数据结构。世界状态树存储帐户状态，表示帐户有多少钱。交易树记录可以更新世界状态树的交易，并且不可变地存储在区块链中以证明活动历史。Receipt trie 代表交易的结果，可以对外查询。我希望这篇文章有助于加深您对以太坊的了解。 参考 深入了解以太坊的世界状态 以太坊解释：默克尔树、世界状态、交易等 了解以太坊中的 Trie 数据库 Transaction Trie 和 Receepts Trie 之间的关系 以太坊区块架构 以太坊中的数据结构| 第 1 集：递归长度前缀 (RLP) 编码/解码。 Modified Merkle Patricia Trie——以太坊如何拯救一个状态 原文 https://medium.com/@eiki1212/ethereum-state-trie-architecture-explained-a30237009d4e
keccak应用 在以太坊中，用keccak哈希算法来计算公钥的256位哈希，再截取这256位哈希的后160位哈希作为地址值。 keccak和sha3的区别 sha3由keccak标准化而来，在很多场合下Keccak和SHA3是同义词，但在2015年8月SHA3最终完成标准化时，NIST调整了填充算法：SHA3-256(M) = KECCAK [512] (M || 01, 256)。所以标准的NIST-SHA3就和keccak计算的结果不一样。 以太坊在开发的时候sha3还在标准化中，所以采用了keccak，所以Ethereum和Solidity智能合约代码中的SHA3是指Keccak256，而不是标准的NIST-SHA3，为了避免混淆，直接在合约代码中写成Keccak256是最清晰的 为何推出sha3 推出sha3不是因为sha2出现了漏洞，只是当时学术界对于sha1被成功碰撞的担忧，但目前基于NIST的建议，sha2和sha3都是属于可以安全商用的哈希算法，sha3相当于多了一种安全选择，比特币选用的就是sha2（SHA256）。 参考 https://ethereum.stackexchange.com/questions/550/which-cryptographic-hash-function-does-ethereum-use https://www.cnblogs.com/HachikoT/p/12792362.html
以太坊核心存储结构：Merkle-Patricia-Tree（前缀树与默克尔树的结合体），以太坊中的交易树、交易收据树、账户树以及合约存储树均使用该树索引。 该树分为三种类型节点：branch（分支，17个元素的元组）、extension（扩展，2个元素的元组）、leaf（叶子节点，2个元素的元组），因此为了区分 extension 与 leaf 节点，使用 key 的第一个 16 进制字符，其中 0000 与 0001 均代表扩展节点，0010 与 0011 均代表叶子节点，也就是说使用倒数第二位来区分 extension 与 leaf 节点。最后一位的 0、1 分别表示了该 key 原先为偶数个 16 进制字符与奇数个 16 进制字符，也就意味着为 0 时，需要填充另外的0000。 账户在以太坊中的存储 下面来看一下以太坊底层存储中是如何实现账户存储的： 目前以太坊中存在两个账户： 0xa3ac96fbe4b0dce5f6f89a715ca00934d68f6c37 0x0f5578914288da3b9a3f43ba41a2e6b4d3dd587a 通过使用编写的拉取以太坊底层存储的 python 脚本，拉取目前底层存储的账户数据（脚本的具体用法有所改变，详见这里）： 其中前一项是以太坊在底层中真正存储的 key，与账户地址的对应关系如下： 也就是说，以太坊存储账户数据的时候，会将账户地址进行一次keccak256 哈希计算 此时，账户树的形状如下： 以太坊中会对节点使用 RLP 编码来存储在底层数据 leveldb 或 rocksdb 中，存储形式为 。所以在上图的槽中，slot 1 实际存储的是 sha3(rlp(leaf1))。 合约在以太坊中的存储 下面来看一下智能合约中的数据是如何存储在以太坊中的： 智能合约中的每一个状态变量都有一个 position，以太坊中每一个 storage slot 为 32 个字节，即 256 位，solidity 编译器会尽量将变量装到同一个 storage slot 中去，对于装不下的，会重新分配 storage slot，遇到 mapping、struct 等类型的变量时，编译器会自动重新分配 storage slot。 该代码存在于账户下，该合约的地址为 0xfe5eeb229738ab87753623a81a42656bcde30a67，contract address = sha3(rlp.encode([creator address, nonce])) 该账户在底层数据库中存储的 key 为 // geth console 环境中 > web3.sha3("0xfe5eeb229738ab87753623a81a42656bcde30a67", {encoding : "hex"}) 0x886f7bfb7a4887d716ec4fbb06a8bf35fc1972d2962590248ffe6271e77ac7c1 // python In [50]: '\xed\xa8}\x9d\xeb\xa5\xbb\xc6O\xa7\'B\xf5\x84"\xaa\xf4f\x9e\xaai)\xe2\xf2_\xa60D\x8a\x0c\x7fJ'.encode("hex") Out[50]: 'eda87d9deba5bbc64fa72742f58422aaf4669eaa6929e2f25fa630448a0c7f4a' 我们来一一分析： 我们可以看到在相应的位置上分别存储了相应的值， 对于 mapping 来说，其中元素存储的position如下： sha3(LeftPad32(key, 0), LeftPad32(map position, 0)) 所以有： 由于 mapping 中 value 为一个 struct，size 大于 256 位，因此，在存储的位置按顺位加 1，如上图所示。 来看一下，动态数组在以太坊底层存储形式： 其中，在位置 5 存储动态数组的长度，然后以位置 sha3(5) 开始顺序存放数组元素。 --- update --- mapping中的 key 也可以是 string 形式，假设 mapping(string => string)，key = "11", value = "22", 则在 eth 底层中存储的 key 为： // 3131 代表字符串 "11"，后面的 32 个 0 代表 map 的 position > web3.sha3("31310000000000000000000000000000000000000000000000000000000000000000", {encoding : "hex"}) "0x756ab6158180196289fbd030ff61972bf49c0e51dbf603d0dfaf6b1d3f0e49a6" // 0x3232...04 是 bytes(string) 在底层的存储表达形式，后面的 04 代表字符串的长度为 2， 前面的 3232 代表真正存储的字符串 "22" > eth.getStorageAt("0xf8a7e4fb488d5e0426012592c5d66e44dffa6cb7", "0x756ab6158180196289fbd030ff61972bf49c0e51dbf603d0dfaf6b1d3f0e49a6") "0x3232000000000000000000000000000000000000000000000000000000000004" key = "1111111111111111111111111111111111", len(key) = 34; value = "2222222222222222222222222222222", len(value) = 31 > web3.sha3("313131313131313131313131313131313131313131313131313131313131313131310000000000000000000000000000000000000000000000000000000000000000", {encoding : "hex"}) "0xeb5b36d98f0c746023b3b0e91319a7ee8cb743f75df9f8ce513c5120487cdac3" // 最后的 3e 代表 31 个字节长 > eth.getStorageAt("0xf8a7e4fb488d5e0426012592c5d66e44dffa6cb7", "0xeb5b36d98f0c746023b3b0e91319a7ee8cb743f75df9f8ce513c5120487cdac3") "0x323232323232323232323232323232323232323232323232323232323232323e" key = "1111111111111111111111111111111111", len(key) = 34; value = "22222222222222222222222222222222", len(value) = 32 // 41 代表字符串长度，为了与小于32个字节的长度区分，这里加了 1，所以算长度时：(0x41-1)/2 = 32 个字节 > eth.getStorageAt("0xf8a7e4fb488d5e0426012592c5d66e44dffa6cb7", "0xeb5b36d98f0c746023b3b0e91319a7ee8cb743f75df9f8ce513c5120487cdac3") "0x0000000000000000000000000000000000000000000000000000000000000041" // 对该 value 对应的 key 再次进行哈希，用于存放真正字符串 > web3.sha3("0xeb5b36d98f0c746023b3b0e91319a7ee8cb743f75df9f8ce513c5120487cdac3", {encoding : "hex"}) "0x2b3b0a6d0771d1a8fa6f89276ead655b7a0684e2a22d9290bcf4f8944f05b504" > eth.getStorageAt("0xf8a7e4fb488d5e0426012592c5d66e44dffa6cb7", "0x2b3b0a6d0771d1a8fa6f89276ead655b7a0684e2a22d9290bcf4f8944f05b504") "0x3232323232323232323232323232323232323232323232323232323232323232" 转载自：https://ethereum.iethpay.com/ethereum-core-storage.html