您正在查看: Ethereum-优秀转载 分类下的文章

以太坊状态树架构解释

解释以太坊状态试图加深对以太坊区块链的了解。

介绍

这篇文章解释了以太坊状态树。复仇通常被称为“世态町ñ 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

Ethereum 中keccak和sha3的区别

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

以太坊合约中是否可以获取交易hash?

答案:不能
txhash 是keccak256(signedTransaction).
此 keccak256 函数可用作 Solidity 函数 http://solidity.readthedocs.io/en/v0.4.21/units-and-global-variables.html

所以你需要构造,signedTransaction因为这个值没有暴露于可靠性,参见。https://stackoverflow.com/questions/49803424/how-can-we-access-rlp-encoded-signed-raw-transaction-in-solidity

signedTransaction 需要的参数是

  1. nonce
  2. gas price
  3. gas limit
  4. to
  5. value in wei
  6. data
  7. ecdsaV
  8. ecdsaR
  9. ecdsaS

值 3 不是直接可用的,但您可以在代码执行的任何时候获取当前剩余的 gas,并从中计算出执行开始后可用的气体量。值 1、7、8 和 9(nonce 和签名值)不能使用solidity,也不能使用汇编代码(可以在solidity 源代码文件中内联编写)。所以很遗憾该问题无法解决。

https://ethereum.stackexchange.com/questions/45648/how-to-calculate-the-assigned-txhash-of-a-transaction
https://github.com/ethereum/EIPs/issues/901

为什么LINK使用ERC-677标准发行token

等等,LINK不是ERC20吗,怎么又成了ERC677了?
别急,我们先从ERC20开始说起。

ERC20是一套协议标准,代码角度来说就是一套接口API。在这个协议标准下,只要实现了协议标准所规定的方法,都可以作为ERC20代币的实现。协议规定必须实现的方法有:

// 1. 代币发行总量
function totalSupply() public view returns (uint256)

// 2. _owner账户的代币余额
function balanceOf(address _owner) public view returns (uint256 balance)

// 3. 转移_value数量的代币到_to地址
function transfer(address _to, uint256 _value) public returns (bool success)

// 4. 从_address地址转移_value数量的代币到_to地址
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)

// 5. 允许_spender可以提取总额为_value数量的代币,提取不限次数
function approve(address _spender, uint256 _value) public returns (bool success)

// 6. 返回_spender还可以从_owner提取的代币数量
function allowance(address _owner, address _spender) public view returns (uint256 remaining)

除了这几个方法,ERC20还规定了两个事件:

// 当成功转移token时,触发Transfer事件,记录转账的发送方、接收方和转账金额
event Transfer(address indexed _from, address indexed _to, uint256 _value)

// 当调用approval函数成功时,触发Approval事件,记录批准的所有方、获取方和批准金额
event Approval(address indexed _owner, address indexed _spender, uint256 _value)

以上基本上就是ERC20代币的全部内容了。由于LINK代币不仅仅是代币,还承担了链上与链下数据传递的功能,所以如果使用ERC20代币的标准无法满足这个需求,于是LINK选择了ERC677协议标准来实现。

ERC677标准是ERC20的一个扩展,它继承了ERC20的所有方法和事件,由Chainlink的CTO Steve Ellis首次提出。ERC677除了包含了ERC20的所有方法和事件之外,增加了一个transferAndCall 方法:

function onTokenTransfer(address from, uint256 amount, bytes data) returns (bool success)

接收合约就可以在这个方法中定义自己的业务逻辑,可以在发生转账的时候自动触发。换句话说,智能合约中的业务逻辑,可以通过代币转账的方式来触发自动运行。这就给了智能合约的应用场景有了很大的想象空间。比如LINK的token合约就是一个ERC677合约,而Chainlink的Oracle合约,是一个可以接收ERC677的合约,它含有onTokenTransfer方法,可以在收到LINK的转账的时候执行预言机相关的业务逻辑。

LINK token contract:

...

  /**
  * @dev 转移token到合约地址,并携带额外数据
  * @param _to 转到的地址
  * @param _value 转账金额
  * @param _data 传递给接受合约的额外数据
  */
  function transferAndCall(address _to, uint _value, bytes _data)
    public
    returns (bool success)
  {
    super.transfer(_to, _value);
    Transfer(msg.sender, _to, _value, _data);
    if (isContract(_to)) {
      contractFallback(_to, _value, _data);
    }
    return true;
  }

...

Oracle 合约:

...

  /**
    * @notice 在LINK通过`transferAndCall`方法发送到合约时被调用
    * @dev 负载数据的前两个字节会被`_sender`和 `_amount`的值覆盖来保证正确性。并会调用oracleRequest方法
    * @param _sender 发送方地址
    * @param _amount 发送的LINK数量(单位是wei)
    * @param _data 交易的负载数据
    */
  function onTokenTransfer(
    address _sender,
    uint256 _amount,
    bytes _data
  )
    public
    onlyLINK
    validRequestLength(_data)
    permittedFunctionsForLINK(_data)
  {
    assembly {
      // solium-disable-next-line security/no-low-level-calls
      mstore(add(_data, 36), _sender) // ensure correct sender is passed
      // solium-disable-next-line security/no-low-level-calls
      mstore(add(_data, 68), _amount)    // ensure correct amount is passed
    }
    // solium-disable-next-line security/no-low-level-calls
    require(address(this).delegatecall(_data), "Unable to create request"); // calls oracleRequest
  }

...

总结:

LINK代币合约是ERC677合约,它是ERC20合约的一个扩展,兼容ERC20协议标准。它可以在转账时携带数据,并触发接收合约的业务逻辑,这一特点可以帮助智能合约扩大应用场景。

参考

https://eips.ethereum.org/EIPS/eip-20
http://blockchainers.org/index.php/2018/02/08/token-erc-comparison-for-fungible-tokens/
https://github.com/ethereum/EIPs/issues/677
https://etherscan.io/address/0x514910771af9ca656af840dff83e8264ecf986ca#code
https://etherscan.io/address/0x64fe692be4b42f4ac9d4617ab824e088350c11c2#code

转载自:https://learnblockchain.cn/article/588