以太坊合约中数据的底层存储

C/C++程序员在进行Solidity开发的时候往往会想:Solidity中的结构体是怎么存储的呢?会不会进行数据对齐呢?怎样定义结构体会使访问效率改一些呢?
我们来探索下以太坊合约的数据在底层的存取机制。参考文章:这个,以及它的翻译

巨大的数组

合约中的所有的数据都存在这个巨大的数组中,以下简称为“大数组”

  • 数组长度为2^256 - 1
  • 数组每个元素32Byte。
  • 数组是稀疏的——不会一下子生成整个数组存在链上
  • 值为0的元素不需要存储
    以太坊合约中数据的底层存储_第1张图片

本文中我们使用如下合约代码作为例子:

contract StorageTest {
    uint256 a;     // slot 0
    uint256[2] b;  // slots 1-2

    struct Entry {
        uint256 id;
        uint256 value;
    }
    Entry c;       // slots 3-4
    Entry[] d;     // slot 5 for length, keccak256(5)+ for data

    mapping(uint256 => uint256) e;    // slot 6, data at h(k . 6)
    mapping(uint256 => uint256) f;    // slot 7, data at h(k . 7)

    mapping(uint256 => uint256[]) g;  // slot 8
    mapping(uint256 => uint256)[] h;  // slot 9
}

固定长度的数据结构

普通内置类型(uint8, uint32,bool 等等)、定长数组、结构体变量,都属于固定长度的数据。

  • 固定长度的数据结构使用声明顺序(原文称之为slot)做数组的索引(下标),存储在大数组中
  • 结构体占用的数组元素个数取决于成员的个数。
  • 编译时刻即确定了这些数据存储的位置
  • 示例代码中,a、b、c为固定长度的数据,他们在大数组中的位置如下图所示
    以太坊合约中数据的底层存储_第2张图片

不定长度的数据结构

动态数组、mapping属于不定长数据结构,这两类结构在编译期无法确定空间大小。

  • 动态数组的长度存在大数组中,位置为该动态数组声明的序号(原文称之为slot)。
  • 动态数组的第一个元素存储在 hash(序号)处,序号即为该数组被声明的序号。
  • mapping成员在大数组中的位置,由 “该变量的成员的key” + “该变量声明的位置” 合起来的hash确定。
    举例:
  • 动态数组d的slot为5(c占用了两个slot)。故大数组的下标5处存放d的长度。
  • 动态数组的第一个元素的下标为: keccak256(5)
  • mapping e 的slot为6,但大数组的下标6处存放的值恒为0、
  • mapping内成员 e[“hello”] 存放的位置为 keccak256(“hello”,6)
  • 下图为 e[123] 以及 e[42]在大数组中的位置,分别为 keccak256(123,6)与 keccak256(42,6)
    以太坊合约中数据的底层存储_第3张图片

复合数据结构

通过上述分析,我们可以写出定位动态数组元素位置的函数与定位mapping元素位置的函数

function arrLocation(uint256 slot, uint256 index, uint256 elementSize)public pure returns (uint256){
    return uint256(keccak256(slot)) + (index * elementSize);
}
function mapLocation(uint256 slot, uint256 key) public pure returns (uint256) {
    return uint256(keccak256(key, slot));
}

示例代码中的g、h便是典型的复合数据结构。

  • g[“haha”][5]元素的位置这样计算: index = arrLocation(mapLocation(8, “haha”), 5, 1);
  • h[4][“hehe”]元素的位置这样计算: index = mapLocation(attLocation(9, 4, 1), “hehe”);
  • 规则总结:先计算复合结构本身在大数组中的位置(slotFather)、再根据slotFather计算子结构的位置,一直递推到最终的基本元素。

合约数据最终会存储在哪里?

首先澄清一下,合约数据指的是合约内部的数据,而不是指合约本身的字节码。
答案是:全节点的EVM中

合约内的数据的存数本质上是一次代码的执行,EVM便是负责执行代码的。块只记录最终的Merkel根。事实上,不光合约内的数据没有直接记录在链上的块中,连最基本的账户的余额这个信息也没有直接记录在链上的块中。块上只保存TransactionToot、StateRoot、ReceiptToot 三个Merkel root便可验证合约数据、账户余额等信息。

你可能感兴趣的:(BlockChain,Solidity)