当前位置: 首页 > news >正文

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

相关文章:

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

what? MPT树是一种数据结构,用于在以太坊区块链中高效地存储和检索账户状态、交易历史和其他重要数据。MPT树的设计旨在结合Merkle树和Patricia树的优点,以提供高效的数据存储和验证 MPT树由四种类型的节点组成: **扩展节点&…...

【代码随想录算法训练营第三十五天】 | 1005.K次取反后最大化的数组和 134.加油站 135.分发糖果

贪心章节的题目,做不出来看题解的时候,千万别有 “为什么这都没想到” 的感觉,想不出来是正常的,转变心态 “妙啊,又学到了新的思路” ,这样能避免消极的心态对做题效率的影响。 134. 加油站 按卡哥的思路…...

桌面应用开发框架比较:Electron、Flutter、Tauri、React Native 与 Qt

在当今快速发展的技术环境中,对跨平台桌面应用程序的需求正在不断激增。 开发人员面临着选择正确框架之挑战,以便可以高效构建可在 Windows、macOS 和 Linux 上无缝运行的应用程序。 在本文中,我们将比较五种流行的桌面应用程序开发框架&…...

学习笔记丨嵌入式BI分析的12个关键功能

编者注:以下内容节选编译自嵌入式分析厂商Qrvey发表的《What is Embedded Analytics?》(什么是嵌入式分析)一文,作者为Qrvey产品市场主管Brian Dreyer。 什么是嵌入式分析? 嵌入式分析是指能够将数据分析的特性和功…...

PostgreSQL17优化器改进(3)在使用包含操作符<@和@>时优化范围查询

PostgreSQL17优化器改进&#xff08;3&#xff09;在使用包含操作符<和>时优化范围查询 本文将介绍PostgreSQL 17服务端优化器在使用包含操作符<和>时优化范围查询。其实在在第一眼看到官网网站的对于该优化点的时候&#xff0c;可能是由于缺乏对于范围类型的认知…...

【因果推断python】32_合成控制2

目录 合成控制作为线性回归的一种实现​编辑 合成控制作为线性回归的一种实现 为了估计综合控制的治疗效果&#xff0c;我们将尝试构建一个类似于干预期之前的治疗单元的“假单元”。然后&#xff0c;我们将看到这个“假单位”在干预后的表现。合成控制和它所模仿的单位之间的…...

Linux-笔记 全志平台OTG虚拟 串口、网口、U盘笔记

前言&#xff1a; 此文章方法适用于全志通用平台&#xff0c;并且三种虚拟功能同一时间只能使用一个&#xff0c;原因是此3种功能都是内核USB Gadget precomposed configurations的其中一个选项&#xff0c;只能单选&#xff0c;不能多选&#xff0c;而且不能通过修改配置文件去…...

Qt实现SwitchButton滑动开关按钮组件

概述 使用Qt如何制作一个滑动开关按钮&#xff0c;同类的文章和代码网上很多&#xff0c;但很多都是pyqt编写的&#xff0c;也有c编写的&#xff0c;大家可以参考. 我这里主要是实现了一个滑动按钮&#xff0c;富有滑动动画和文字&#xff0c;话不多说&#xff0c;上代码 自定义…...

C++进阶:继承

文章目录 继承的概念继承的定义方式继承关系和访问限定符基类和派生类对象的赋值转换继承中的作用域派生类中的默认成员函数构造函数拷贝构造函数赋值拷贝函数析构函数 总结 继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允…...

SFTP工具

SFTP工具 工具类配置类调用 工具类 Slf4j Component public class SFTPUtils {Resourceprivate SftpConfig sftpConfig;Session session null;Channel channel null;/*** 网络图片url** param fileUrl* throws JSchException*/public String uploadFileSFTP(String fileUrl) …...

服务器数据恢复—vxfs文件系统元数据被破坏的数据恢复案例

服务器存储数据恢复环境&#xff1a; 某品牌MSA2000服务器存储中有一组由8块SAS硬盘组建的raid5磁盘阵列&#xff0c;其中包含一块热备盘。分配了6个LUN&#xff0c;均分配给HP-Unix小机使用。磁盘分区由LVM进行管理&#xff0c;存放的数据主要为Oracle数据库及OA服务端。 服务…...

【SCAU数据挖掘】数据挖掘期末总复习题库简答题及解析——上

1.K-Means 假定我们对A、B、C、D四个样品分别测量两个变量&#xff0c;得到的结果见下表。 样品 变量 X1X2 A 5 3 B -1 1 C 1 -2 D -3 -2 利用K-Means方法将以上的样品聚成两类。为了实施均值法(K-Means)聚类&#xff0c;首先将这些样品随意分成两类(A、B)和(C、…...

云时代的Java:在云环境中实施Java的最佳实践

引言 云计算已经成为现代软件开发不可或缺的一部分&#xff0c;它提供了灵活性、可扩展性和成本效益。对于Java开发者来说&#xff0c;掌握在云环境中部署和管理Java应用的最佳实践是至关重要的。本文将探讨一些关键策略&#xff0c;帮助你最大化Java在云平台上的性能和效率。…...

STL - 常用算法

概述&#xff1a; 算法主要是由头文件<algorithm><functional><numeric>组成<algorithm>是所有STL头文件中最大的一个&#xff0c;范围涉及比较、 交换、查找、遍历操作、复制、修改等等<numeric>体积很小&#xff0c;只包括几个在序列上面进行…...

Qt | QTextStream 类(文本流)

01、字符编码 1、怎样将字符转换为二进制形式进行存储,存在一个编码的问题,通常都需进行两次编码, 2、字符集:字符的第一次编码是将字符编码为与一个数值(如一个 10 进制整数)相对应,比如把字符 A 编码为 10 进制的 65,B 编码为 66 等。把每一个字符都编码为与一个数值…...

Python学习笔记7:入门知识(七)

前言 之前说过我更换了新的学习路线&#xff0c;现在是根据官方文档和书籍Python crash course来进行学习的&#xff0c;在目前的学习中&#xff0c;对于之前的知识有一些遗漏&#xff0c;这里进行补充。 学习资料有两个&#xff0c;书籍中文版PDF&#xff0c;关注我私信发送…...

如何翻译和本地化游戏?翻译访谈

如何翻译和本地化游戏&#xff1f;这个过程的技术细节有哪些&#xff1f;游戏翻译不同于电影翻译。Logrus IT游戏本地化部门负责人阿列克谢费奥多罗夫&#xff08;Alexey Fedorov&#xff09;在接受RUDN语言学系外语系教授和研究人员的采访时谈到了这一点&#xff0c;他是由尤利…...

[C++] 从零实现一个ping服务

&#x1f4bb;文章目录 前言ICMP概念报文格式 Ping服务实现系统调用函数具体实现运行测试 总结 前言 ping命令&#xff0c;因为其简单、易用等特点&#xff0c;几乎所有的操作系统都内置了一个ping命令。如果你是一名C初学者&#xff0c;对网络编程、系统编程有所了解&#xff…...

2024网络安全学习路线 非常详细 推荐学习

关键词&#xff1a;网络安全入门、渗透测试学习、零基础学安全、网络安全学习路线 首先咱们聊聊&#xff0c;学习网络安全方向通常会有哪些问题 1、打基础时间太长 学基础花费很长时间&#xff0c;光语言都有几门&#xff0c;有些人会倒在学习 linux 系统及命令的路上&#…...

STM32F103ZET6_HAL_CAN

1定义时钟 2定义按键 按键上拉电阻 3开启串口 4打开CAN&#xff08;具体什么意思上一篇讲了&#xff09; 5生成代码 /* USER CODE BEGIN Header */ /********************************************************************************* file : main.c* brief …...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

Python实现prophet 理论及参数优化

文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候&#xff0c;写过一篇简单实现&#xff0c;后期随着对该模型的深入研究&#xff0c;本次记录涉及到prophet 的公式以及参数调优&#xff0c;从公式可以更直观…...

关于 WASM:1. WASM 基础原理

一、WASM 简介 1.1 WebAssembly 是什么&#xff1f; WebAssembly&#xff08;WASM&#xff09; 是一种能在现代浏览器中高效运行的二进制指令格式&#xff0c;它不是传统的编程语言&#xff0c;而是一种 低级字节码格式&#xff0c;可由高级语言&#xff08;如 C、C、Rust&am…...

(转)什么是DockerCompose?它有什么作用?

一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用&#xff0c;而无需手动一个个创建和运行容器。 Compose文件是一个文本文件&#xff0c;通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...

AI,如何重构理解、匹配与决策?

AI 时代&#xff0c;我们如何理解消费&#xff1f; 作者&#xff5c;王彬 封面&#xff5c;Unplash 人们通过信息理解世界。 曾几何时&#xff0c;PC 与移动互联网重塑了人们的购物路径&#xff1a;信息变得唾手可得&#xff0c;商品决策变得高度依赖内容。 但 AI 时代的来…...

Redis:现代应用开发的高效内存数据存储利器

一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发&#xff0c;其初衷是为了满足他自己的一个项目需求&#xff0c;即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源&#xff0c;Redis凭借其简单易用、…...

基于Springboot+Vue的办公管理系统

角色&#xff1a; 管理员、员工 技术&#xff1a; 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能&#xff1a; 该办公管理系统是一个综合性的企业内部管理平台&#xff0c;旨在提升企业运营效率和员工管理水…...

ZYNQ学习记录FPGA(一)ZYNQ简介

一、知识准备 1.一些术语,缩写和概念&#xff1a; 1&#xff09;ZYNQ全称&#xff1a;ZYNQ7000 All Pgrammable SoC 2&#xff09;SoC:system on chips(片上系统)&#xff0c;对比集成电路的SoB&#xff08;system on board&#xff09; 3&#xff09;ARM&#xff1a;处理器…...

轻量级Docker管理工具Docker Switchboard

简介 什么是 Docker Switchboard &#xff1f; Docker Switchboard 是一个轻量级的 Web 应用程序&#xff0c;用于管理 Docker 容器。它提供了一个干净、用户友好的界面来启动、停止和监控主机上运行的容器&#xff0c;使其成为本地开发、家庭实验室或小型服务器设置的理想选择…...

Docker、Wsl 打包迁移环境

电脑需要开启wsl2 可以使用wsl -v 查看当前的版本 wsl -v WSL 版本&#xff1a; 2.2.4.0 内核版本&#xff1a; 5.15.153.1-2 WSLg 版本&#xff1a; 1.0.61 MSRDC 版本&#xff1a; 1.2.5326 Direct3D 版本&#xff1a; 1.611.1-81528511 DXCore 版本&#xff1a; 10.0.2609…...