MPT(merkle Patricia trie )及理解solidity里的storage

what?

MPT树是一种数据结构,用于在以太坊区块链中高效地存储和检索账户状态、交易历史和其他重要数据。MPT树的设计旨在结合Merkle树和Patricia树的优点,以提供高效的数据存储和验证

MPT树由四种类型的节点组成:

**扩展节点(Extension Node)**:存储一个前缀和一个指向下一个节点的引用。它的作用是为了压缩树的高度,提高存储效率。

**分支节点(Branch Node)**:包含16个子节点的数组,每个子节点对应一个16进制字符(0到f)。这些子节点可以是叶子节点、扩展节点或其他分支节点,用于构建树的层次结构。

**叶子节点(Leaf Node)**:包含键值对,存储着具体的数据。在以太坊中,这些数据通常是账户的状态信息,如余额、合约代码等。

**空节点(Null Node)**:表示空指针或空链接,用于表示树的末端。

是什么?
  1. Merkel Patricia Tree(MPT),翻译为梅克尔-帕特里夏树
  2. MPT提供了一个基于密码学验证的底层数据结构,用来存储键值对(key-value)关系
  3. MPT是完全确定性的,这是指在一颗MPT上一组键值对是唯一确定的,相同内容的键可以保证找到同样的值,并且有同样的根哈希(root hash)
  4. MPT 的插入、查找、删除操作的时间复杂度都是O(log(n))相对于其它基于复杂比较的树结构(比如红黑树),MPT更容易理解,也更易于编码实现
字典树(trie 前缀树)Data Structure Visualization

基数树(Radix Tree 压缩前缀树)

基数树又叫压缩前缀树(compact prefix tree),是一种空间优化后的字典树,其中如果一个节点只有唯一的子节点那么这个子节点就会与父节点合并存储。

在一个标准的基数树里,每个节点存储的数据如下:[i0, i1, .. in, value]

  • 这里的 i0,i1...,in 表示定义好的字母表中的字符,字母表中一共有n+1个字符,这颗树的基数(radix)就是n+1
  • value 表示这个节点中最终存储的值
  • 每一个i0 到in 的“槽位”存储的或者是nul,或者是指向另一节点的指针
  • 用节点的访问路径表示key,用节点的最末位置存储 value:这就实现了一个基本的键值对存储
Merkle Tree

也被称作哈希树(HashTree),以数据块的hash值作为叶子节点存储值。梅克尔树的非叶子节点存储其子节点内容串联拼接后的hash值。

帕特里夏树(Patricia Tree)
  1. 如果一个基数树的“基数’(radix)为2或2的整数次幂就被称为“帕特里夏树”,有时也直接认为帕特里夏树就是基数树
  2. 以太坊中采用 Hex字符作为key的字符集,也就是基数为16的帕特里夏树
  3. 以太坊中的树结构,每个节点可以有最多16个子节点,再加上 value,所以共有 17 个“插槽”(slot)位置
  4. 以太坊中的帕特里夏树加入了一些额外的数据结构,主要是为了解决效率问题
MPT(Merkel Patricia Tree)
  1. 梅克尔-帕特里夏树是梅克尔树和帕特里夏树的结合
  2. 以太坊中的实现,对key采用 Hex编码,每个Hex字符就是一个nibble(半字节)
  3. 遍历路径时对一个节点只访问它的一个nibble,大多数节点是一个包含17个元素的数组;中16个分别以hex字符作为索引值,存储路径中下一个nibble的指针;另一个存储如果路径到此已遍历结束,需要返回的最终值。这样的节点叫做“分支节点”(branch node)
  4. 分支节点的每个元素存储的是指向下一级节点的指针。与传统做法不同,MPT是用所指向节点的hash来代表这个指针的;每个节点将下个节点的hash作为自己存储内容的一部分,这样就实现了Merkel树结构,保证了数据校验的有效性
MPT节点分类

MPT中的节点有以下几类:

  • 空节点(NULL)
    • 表示空字符串
  • 分支节点(branch)
    • 17个元素的节点,结构为[v0..... v15,vt]
  • 叶子节点(leaf)
    • 拥有两个元素,编码路径encodedPath 和值 value
  • 扩展节点(extension)
    • 拥有两个元素,编码路径encodedPath 和键 key
MPT中数据结构的优化
  • 对于64个字符的路径长度,很有可能在某个节点处会发现,下面至少有一段路径没 有分叉;这很难避免
  • 我们当然可以依然用标准的分支节点来表示,强制要求这个节点必须有完整的16个索引,并给没有用到的那15个位置全部赋空值;但这样有点蠢
  • 通过设置“扩展节点”,就可以有效地缩短访问路径,将几长的层级关系压缩成一个键值对,避免不必要的空间浪费
  • 扩展节点(extensionnode)的内容形式是[encodedPath,key],,其中 encodedPath包含了下面不分叉的那部分路径,key是指向下一个节点的指针(hash,也即在底层db中的存储位置)
  • 叶子节点(leafnode):如果在某节点后就没有了分叉路径那这是一个叶子节点,它的第二个元素就是自己的value

MPT紧凑编码(compact coding)
  • 路径压缩的处理相当于实现了压缩前缀树的功能;不过路径表示是Hex字符串(nibbles),而存储却是以字节(byte)为单位的,这相当于浪费了一倍的存储空间
  • 我们可以采用一种紧凑编码(compactcoding)方式,将两个 nibble 整合在一个字节中保存,这就避免了不必要的浪费
  • 这里就会带来一个问题:有可能nibble 总数是一个奇数,而数据总是以字节形式存储的,所以无法区分nibble1和nibbles01;这就使我们必须分别处理奇偶两种情况
  • 为了区分路径长度的奇偶性,我们在encodedPath中引入标识位
    • 我们在encodedPath中,加入一个nibble 作为前缀,它的后两位用来标识节点类型和路径长度的奇偶性

    • MPT中还有一个可选的“结束标记”(用T表示),值为0x10十进制的16),它仅能在路径末尾出现,代表节点是一个最终节点(叶子节点)
    • 如果路径是奇数,就与前缀nibble凑成整字节;如果是偶数,则前缀nibble后补0000构成整字节

how

MPT树的工作原理如下:

  • 根据键值对构建树:将键值对插入到MPT树中,根据键的字节表示构建树的路径
  • 哈希计算:每个节点存储其子节点的哈希值,以确保数据的完整性和安全性
  • 路径压缩:利用扩展节点将具有相同前缀的节点合并,以减少树的高度和存储空间
  • 快速检索:通过树的根节点可以快速检索任意键的值,而不必遍历整个树

以太坊中的MPT

  1. StateDB结构,我们可以看到它有一个stateObjects字段,是地址到stateObjects的映射表(记得 "State Root"Merkle Patricia Trie是以太坊地址到以太坊账户的映射,stateObject是一个正在被修改的以太坊账户。)
  2. stateObject结构,我们可以看到它有一个数据字段,属于StateAccount类型(记得在文章的前面,我们将Ethereum账户映射到Geth中的StateAccount)。
  3. StateAccount结构,我们已经学习了这个结构,它代表一个以太坊账户,Root字段代表我们之前讨论的 "Storage Root"。
    在这个阶段,一些拼图的碎片开始拼凑起来。现在我们有了背景,可以看到一个新的 "以太坊账户"(StateAccount)是如何初始化的。

初始一个新的以太坊用户

为了创建一个新的StateAccount,我们需要与statedb.go代码和StateDB结构交互。

StateDB有一个createObject函数,可以创建一个新的stateObject,并将一个空的StateAccount传给它。这实际上是创建一个空的"以太坊账户"。

下图详细说明了代码流程。

  1. StateDB有一个createObject函数,它接收一个Ethereum地址并返回一个stateObject(记住一个stateObject代表一个正在修改的Ethereum账户。)
  2. createObject函数调用newObject函数,输入stateDB、地址和一个空的StateAccount(记住一个StateAccount=以太坊账户),返回一个stateObject。
  3. 在newObject函数的返回语句中,我们可以看到有许多与stateObject相关的字段,地址、数据、dirtyStorage等。
  4. stateObject的data字段映射到函数中的空StateAccount输入--注意在第103-111行StateAccount中的nil值被赋值。
  5. 创建的stateObject包含初始化的StateAccount作为数据字段被返回。

好了,我们有一个空的stateAccount,接下来我们要做什么?

我们想存储一些数据,为此我们需要使用SSTORE操作码。

  1. 我们从定义了所有EVM操作码的instruction.go文件开始。在这个文件中,我们找到了 "opSstore "函数。
  2. 传入该函数的范围变量包含合同上下文,如堆栈、内存等。我们从堆栈中弹出2个值,并标记为loc(位置的缩写)和val(值的缩写)。
  3. 然后,从堆栈中弹出的2个值以及合约地址一起被用作StateDB对象的SetState函数的输入。SetState函数先用合约地址来检查该合约是否存在一个stateObject,如果不存在,它将创建一个。然后,它在该stateObject上调用SetState,传入StateDB db、相应的key和value值。
  4. stateObject SetState函数对'fake storage'做了一些空值检查,然后检查value是否有变化,如果有变化,则通过journal结构记录变化。
  5. 如果你看一下关于journal结构的代码注释,你会发现journal是用来跟踪状态修改的,以便在出现执行异常或请求撤销的情况下可以恢复这些修改。
  6. 在journal结构被更新后,storageObject的setState函数被调用,入参为key和value。这将更新storageObjects的dirtyStorage。
    好了,我们已经用key和value更新了stateObject的dirtyStorage。这实际上意味着什么,它与我们到目前为止所学的一切有什么关系?

让我们从代码中的dirtyStorage定义继续学习。

  1. dirtyStorage被定义在stateObject结构中,它属于Storage类型,被描述为 "在当前交易执行中被修改的存储条目"。
  2. 与dirtyStorage相对应的类型Storage是common.Hash到common.Hash的简单映射。
  3. 类型Hash只是一个长度为HashLength的数组。
  4. HashLength是一个常数,定义为32
    这对你来说应该很熟悉,一个32字节的key映射到一个32字节的value。这正是我们在EVM深度探讨的第三部分中从概念上看待合约storage存储空间的方式。

你可能已经注意到stateObject中的pendingStorage和originStorage就在dirtyStorage字段的上方。它们都是相关的,在最终确定过程中,dirtyStorage被复制到pendingStorage,而pendingStorage在 trie被更新时又被复制到originStorage。

在 trie 被更新后,StateAccount 的 "存储根 "也将在 StateDB 的 "提交 "中被更新。这将把新的状态写入底层的内存 trie 数据库中。

现在到了拼图的最后一块,SLOAD。

  1. 我们再次从 instructions.go 文件开始,在那里我们可以找到 "opSload "函数。我们使用peek从堆栈的顶部抓取SLOAD的位置(存储槽)。
  2. 我们调用StateDB上的GetState函数,输入合约地址和slot位置。GetState函数返回与该合约地址相关的stateObject。如果返回的stateObject不是空值,则调用该stateObject上的GetState函数。
  3. 在stateObject上的GetState函数对fakeStorage进行了检查,然后对dirtyStorage进行检查。
  4. 如果dirtyStorage存在,返回dirtyStorage映射表中位置key相对应的值。(dirtyStorage代表了合约的最新状态,这就是为什么我们试图首先返回它)
  5. 否则就调用GetCommitedState函数,尝试在storage trie中查找该值。同样需要先检查fakeStorage。
  6. 如果pendingStorage存在,返回pendingStorage映射表中位置key相对应的值。
  7. 如果上述方法都没有返回,就去找originStorage,从那里检索并返回值。
    你会注意到,该函数试图先返回dirtyStorage,然后是pendingStorage,最后是originStorage。这是有道理的,在执行过程中,dirtyStorage是最新的存储映射,其次是pending,然后是originStorage。

一个交易可以多次操作一个存储槽,所以我们必须确保我们有最新的值。

让我们想象一下,在同一交易中,在同一存储槽的SLOAD之前,发生了一个SSTORE。在这种情况下,dirtyStorage将在SSTORE中被更新,在SLOAD中被返回。

到这里,你应该对SSTORE和SLOAD是如何在Geth客户端层面实现的有了了解。它们如何与状态和存储对象互动,以及更新存储槽与更广泛的以太坊 "世界状态 "的关系。

这很难,但你做到了。我猜这篇文章给你留下了比你开始之前更多的问题,但这也是加密货币的乐趣之一。

参考:

彻底理解solidity里的storage | 登链社区 | 区块链技术社区

https://noxx.substack.com/p/evm-deep-dives-the-path-to-shadowy-5a5?s=r

https://www.youtube.com/watch?v=x0Kn0_za2RQ&list=PLmOn9nNkQxJG2agxy_3liL-dJi6jfefTY&index=84

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/26606.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

max code size exceeded

Warning! Error encountered during contract execution [max code size exceeded] 智能合约编译时提示 contracts/core/CORE.sol:15:1: Warning: Contract code size exceeds 24576 bytes (a limit introduced in Spurious Dragon). This contract may not be deployable on m…

小而美的算法技巧:前缀和数组

小而美的算法技巧&#xff1a;前缀和数组 类似动态规划。 class NumArray {private int[] preSum;public NumArray(int[] nums) {preSumnew int[nums.length1];//preSum[0]的前缀和为0for(int i1;i<preSum.length;i){preSum[i]nums[i-1]preSum[i-1];//先计算累加和}}publi…

解锁ChatGPT:从原理探索到GPT-2的中文实践及性能优化

⭐️我叫忆_恒心&#xff0c;一名喜欢书写博客的研究生&#x1f468;‍&#x1f393;。 如果觉得本文能帮到您&#xff0c;麻烦点个赞&#x1f44d;呗&#xff01; 近期会不断在专栏里进行更新讲解博客~~~ 有什么问题的小伙伴 欢迎留言提问欧&#xff0c;喜欢的小伙伴给个三连支…

视频生成模型 Dream Machine 开放试用;微软将停止 Copilot GPTs丨 RTE 开发者日报 Vol.224

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」 &#xff0c;每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE&#xff08;Real-Time Engagement&#xff09; 领域内「有话题的 新闻 」、「有态度的 观点 」、「有意思的 数据 」、「有思考的 文…

2.6数据报与虚电路

数据报 当作为通信子网用户的端系统要发送一个报文时&#xff0c;在端系统中实现的高层协议先把报文拆成若干个带有序号的数据单元&#xff0c;并在网络层加上地址等控制信息后形成数据报分组(即网络层PDU)中间结点存储分组一段很短的时间&#xff0c;找到最佳的路由后&#x…

6.nginx负载均衡

说明 增加服务器的数量,将请求分发到各个服务器上。 将原来请求集中到单个服务器上的情况改为将请求分发到多个服务器上。 案例 浏览器请求地址http://ip/edu/a.html, 负载均衡的效果,平分到8080和8081两台服务上中。 准备工作 tomcat8080配置 tomcat8081配置 直接通过…

Deepstream 应用——去掉矩形框和文字

问题 如何去掉矩形框&#xff1f;以及矩形框上文字&#xff1f; 思路 参照Deepstream用户手册——DeepStream应用及配置文件-CSDN博客 可以看到修改OSD组可以操控矩形框以及文字&#xff1b; 具体方法 若为配置文件将下列项内容修改如下所示&#xff1a; display-text0dis…

【ARMv8/ARMv9 硬件加速系列 1 -- SVE | NEON | SIMD | VFP | MVE | MPE 基础介绍】

文章目录 ARM 扩展功能介绍VFP (Vector Floating Point)SIMD (Single Instruction, Multiple Data)NEONSVE (Scalable Vector Extension)SME (Scalable Matrix Extension)CME (Compute Matrix Engine)MVE (M-profile Vector Extension)MPE (Media Processing Engine)总结 ARM 扩…

嵌入式学习记录6.13(qt day1)

一.思维导图 二.练习&#xff08;简单模拟tim界面&#xff09; 2.1代码 mywidget.cpp #include "mywidget.h"MyWidget::MyWidget(QWidget *parent): QWidget(parent) {this->setWindowTitle("Tim");this->setWindowIcon(QIcon("C:\\Users\\zy\…

微信小程序-uniapp-切换tab时数据列表如何切换?

如图&#xff1a; 这里有两个tab&#xff0c;要保证每次切换后列表保持不变&#xff0c;就必须在运行时要有两个持久化的数据源&#xff0c;每个tab是一个列表&#xff0c;让我们来设计一下这样的数据结构。 首先我们的数据结构是这样的&#xff1a; 体现在vue的data是这样的&a…

Qt多线程之moveToThread()函数

文章目录 一、moveToThread()执行后&#xff0c;当前代码线程没有改变。二、对象执行moveToThread()后&#xff0c;哪些成员加入了子线程1、创建对象时不指定父对象2、对属性对象使用moveToThread加入子线程作用域3、将属性对象的创建放到子线程中执行 三、C内存模型 在使用“继…

ollama系统更改模型存放位置

1.windows 设置完后可以在cmd中检查一下&#xff1a;echo %ollama_models% 2.linux 首先第一步&#xff1a;cd /etc/systemd/system/ 打开配置文件vim ollama.service 第二步&#xff1a;目录下的environment里面分号隔开添加OLLAMA_MODELS环境变量 第三步&#xff1a;source …

【Kadane】Leetcode 918. 环形子数组的最大和【中等】

环形子数组的最大和 给定一个长度为 n 的环形整数数组 nums &#xff0c;返回 nums 的非空 子数组 的最大可能和 。 环形数组 意味着数组的末端将会与开头相连呈环状。形式上&#xff0c; nums[i] 的下一个元素是 nums[(i 1) % n] &#xff0c;nums[i] 的前一个元素是 nums…

工作组局域网-ARP欺骗-攻击防御单双向

免责声明:本文仅做技术交流与学习... 目录 断网限制-单向 环境: 演示: win10: 欺骗前 欺骗后 kali: kali执行命令: win10结果: 劫持数据-双向 欺骗&#xff1a; 网络分析&#xff1a; 防御--动态解析改静态 中间人攻击 断网限制-单向 环境: 靶机:win10 攻击机:kali…

硬件电路基础

说起来在华北理工大学某个实验室当了快一年的硬件部部长&#xff0c;但是能力水平还是在单片机编程和应用层面&#xff08;虽然也很牛逼了&#xff0c;但是我不介意让我更牛逼一点&#xff09;。对于硬件电路的基础还不是很够。在b站偶尔刷到了我们学校隔壁电协一个学长的毕业视…

抖某音号解封释放实名

##抖音账号封禁后如何解封呢 我相信&#xff0c;做过抖音&#xff0c;或者正在做抖音的朋友&#xff0c;都曾面临一种尴尬至极的局面&#xff0c;辛辛苦苦做起来的账号&#xff0c;或者刚刚准备好的账号&#xff0c;在一时之间&#xff0c;竟然被抖音官方封禁了&#xff01; 实…

Java项目:110 springboot夕阳红公寓管理系统的设计与实现

作者主页&#xff1a;舒克日记 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 本系统有管理员&#xff0c;租客 管理员权限操作的功能包括对租客&#xff0c;访客&#xff0c;缴费&#xff0c;维修&#xff0c;留言&#xff0c;公…

OpenCV学习(4.11) OpenCV中的图像转换

1. 目标 在本节中&#xff0c;我们将学习 使用OpenCV查找图像的傅立叶变换利用Numpy中可用的FFT功能傅立叶变换的一些应用我们将看到以下函数&#xff1a;**cv.dft()** &#xff0c;**cv.idft()** 等 理论 傅立叶变换用于分析各种滤波器的频率特性。对于图像&#xff0c;使用…

CoppeliaSim机器人模拟器与Matlab Simulink环境

一、CoppeliaSim机器人模拟器 CoppeliaSim&#xff08;原名V-REP&#xff0c;Virtual Robot Experimentation Platform&#xff09;是一款基于物理引擎的动力学机器人模拟器。它提供了一个集成的开发环境&#xff0c;支持传感器、机械、机器人、环境的系统建模与仿真。Coppeli…

【第9章】“基础工作流”怎么用?(图生图/局部重绘/VAE/更多基础工作流)ComfyUI基础入门教程

🎁引言 学到这里,大家是不是会比较纠结,好像还在持续学习新的东西,未来还有多少基础的东西要学习,才能正常使用ComfyUI呢? 这其实需要转变一个心态。 AI绘画还处于一个快速迭代的过程,隔三岔五的就会有很多新技术、新模型出现,ComfyUI目前同样处于一个快速更新的阶…