【源码阅读】evmⅠ

news2024/11/16 15:39:54

代码位置如下:
代码
参考link
以太坊中有一个很重要的用途是智能合约,而其中evm模块是实现了执行智能合约的虚拟机。evm可以逐条解析执行智能合约的指令。
evm中的核心对象是EVM,代表一个以太坊虚拟机。其内部主要依赖:解释器Interoreter(循环解释执行给定的合约指令,直接遇到退出指令)、配置和状态数据库StateDB(用来提供数据的永久存储和查询)

1、创建EVM

前提是在core/state_processor.go中使用ApplyTransaction函数来处理交易。在这个函数里面,需要创建一个EVM来执行交易中的数据。
使用NewEVM函数来实现创建。

func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig *params.ChainConfig, config Config) *EVM

主要包括:

  1. ctx: 提供访问当前区块链数据和挖矿环境的函数和数据
  2. statedb: 以太坊状态数据库对象
  3. chainConfig: 当前节点的区块链配置信息
  4. vmConfig: 虚拟机配置信息
  5. 解释器
    而解释器的创建是通过函数NewEVMInterpreter实现的。该函数首先要根据以太坊的版本不同,为jumpTable字段进行不同的填充。

2、创建合约

使用EVM.Create函数来创建合约。通过当前合约的创建者地址和其账户中的 Nonce 值,计算出来一个地址值,作为合约的地址。然后将这个地址和其它参数传给 EVM.create 方法。同一份合约代码,每次创建合约时得到的合约地址都是不一样的,因为合约是通过发送交易创建,而每发送一次交易 Nonce 值都会改变。

func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
	contractAddr = crypto.CreateAddress(caller.Address(), evm.StateDB.GetNonce(caller.Address()))
	return evm.create(caller, &codeAndHash{code: code}, gas, value, contractAddr, CREATE)
}

而EVM.create函数才是主要内容。

func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *uint256.Int, address common.Address, typ OpCode) ([]byte, common.Address, uint64, error)

在函数的第一部分,需要进行判断,包括合约的递归调用次数depth是否在限制之内、以及创建者是否有足够的以太币。每次创建完成就需要将创建者的nonce值加一,这样每一次创建合约的地址才会不一样。

	if evm.depth > int(params.CallCreateDepth) {
		return nil, common.Address{}, gas, ErrDepth
	}
	if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
		return nil, common.Address{}, gas, ErrInsufficientBalance
	}
	nonce := evm.StateDB.GetNonce(caller.Address())
	if nonce+1 < nonce {
		return nil, common.Address{}, gas, ErrNonceUintOverflow
	}
	evm.StateDB.SetNonce(caller.Address(), nonce+1)

在完成一系列的判断之后,需要创建合约账户,将交易中规定的金额转到账户中,如果账户已经存在就返回错误。

	if evm.chainRules.IsBerlin {
		evm.StateDB.AddAddressToAccessList(address)
	}
	// Ensure there's no existing contract already at the designated address
	contractHash := evm.StateDB.GetCodeHash(address)
	if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) {
		return nil, common.Address{}, 0, ErrContractAddressCollision
	}
	// Create a new account on the state
	snapshot := evm.StateDB.Snapshot()
	evm.StateDB.CreateAccount(address)
	if evm.chainRules.IsEIP158 {
		evm.StateDB.SetNonce(address, 1)
	}
	evm.Context.Transfer(evm.StateDB, caller.Address(), address, value)

在第三部分中需要创建一个contract对象,其中包括合约在执行过程中的必要信息,比如合约的创建者、合约的地址等。具体函数在contract.go文件里面。在合约创建之后,需要调用run函数来运行合约。如果合约运行成功,并且代码长度没有超过限制MaxCodeSize,就会将合约代码存储到stateDB中的合约账户中并且消耗一定的gas。因为在编译器阶段,会向源代码中插入其它代码,所以存储的是运行后的返回码。

//判断长度
if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize {
		err = ErrMaxCodeSizeExceeded
	}
	// Reject code starting with 0xEF if EIP-3541 is enabled.
	if err == nil && len(ret) >= 1 && ret[0] == 0xEF && evm.chainRules.IsLondon {
		err = ErrInvalidCode
	}
	//没有错误就写入数据库
	if err == nil {
		createDataGas := uint64(len(ret)) * params.CreateDataGas
		if contract.UseGas(createDataGas) {
			evm.StateDB.SetCode(address, ret)
		} else {
			err = ErrCodeStoreOutOfGas
		}
	}

2.1执行合约创建

主要实现在interpreter中的Run函数。首先迭代次数depth的增加和减少是在解释器中实现的,使用到了defer延迟函数。
之后我们判断readOnly和in.readOnly,只有当readOnly为真(即我们需要设置为只读)并且in.readOnly为假(即当前不处于只读状态)时,才会执行大括号内的代码。

	if readOnly && !in.readOnly {
		in.readOnly = true
		defer func() { in.readOnly = false }()
	}

以太坊虚拟机的工作流程:
由solidity语言编写的智能合约,通过编译器编译成bytecode,之后发到以太坊上,以太坊底层通过evm模块支持合约的执行和调用,调用时根据合约获取代码,即合约的字节码,生成环境后载入到 EVM 执行。
所以需要opcode操作码和mem存储以及为了应对高并发情况下的栈资源问题,代码中创建了stack来保存一些被创造但未使用的栈空间。
主要函数内容以及解释如下:

	for {
	//如果处于调试模式,那么会记录执行前的状态信息,包括程序计数器(pc)、剩余的gas和操作的成本。
		if debug {
			// Capture pre-execution values for tracing.
			logged, pcCopy, gasCopy = false, pc, contract.Gas
		}
		// Get the operation from the jump table and validate the stack to ensure there are
		// enough stack items available to perform the operation.
		//从合约中获取当前程序计数器指向的操作
		op = contract.GetOp(pc)
		//从操作表中获取对应的操作
		operation := in.table[op]
		//获取操作的固定gas成本
		cost = operation.constantGas // For tracing
		// Validate stack 对stack进行验证
		if sLen := stack.len(); sLen < operation.minStack {
			return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack}
		} else if sLen > operation.maxStack {
			return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
		}
		//如果剩余的gas不足以执行这个操作,那么返回错误
		if !contract.UseGas(cost) {
			return nil, ErrOutOfGas
		}
		//如果操作有动态的gas成本,那么计算新的内存大小并扩展内存以适应操作
		if operation.dynamicGas != nil {
			// All ops with a dynamic memory usage also has a dynamic gas cost.
			var memorySize uint64
			if operation.memorySize != nil {
				memSize, overflow := operation.memorySize(stack)
				if overflow {
					return nil, ErrGasUintOverflow
				}
				if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
					return nil, ErrGasUintOverflow
				}
			}
			var dynamicCost uint64
			//计算动态的gas成本
			dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize)
			cost += dynamicCost // for tracing
			//如果计算过程中出现错误或者剩余的gas不足以支付动态的gas成本,那么返回错误
			if err != nil || !contract.UseGas(dynamicCost) {
				return nil, ErrOutOfGas
			}
			//如果处于调试模式,那么记录执行后的状态信息
			if debug {
				in.evm.Config.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
				logged = true
			}
			if memorySize > 0 {
				mem.Resize(memorySize)
			}
		} else if debug {
			in.evm.Config.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
			logged = true
		}
		// 执行操作
		res, err = operation.execute(&pc, in, callContext)
		if err != nil {
			break
		}
		//将程序计数器加一,准备执行下一个操作
		pc++
	}

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

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

相关文章

水下蓝牙耳机哪个牌子好?推荐四款高人气力作游泳耳机

在这个充满活力的时代&#xff0c;人们对于生活的追求早已不仅仅局限于日常的琐碎&#xff0c;更多的是对健康、对自我挑战的向往。运动&#xff0c;成为了现代人生活中不可或缺的一部分。而游泳&#xff0c;作为一项既能锻炼全身&#xff0c;又能享受水中美妙的运动&#xff0…

行尾检测论文汇总

文章目录 2023GNSS-Free End-of-Row Detection and Headland Maneuvering for Orchard Navigation Using a Depth Camera 2023 GNSS-Free End-of-Row Detection and Headland Maneuvering for Orchard Navigation Using a Depth Camera 摘要&#xff1a; 果园中基于GPS的导航…

苍穹外卖-day08:导入地址簿功能代码(单表crud)、用户下单(业务逻辑)、订单支付(业务逻辑,cpolar软件)

苍穹外卖-day08 课程内容 导入地址簿功能代码用户下单订单支付 功能实现&#xff1a;用户下单、订单支付 用户下单效果图&#xff1a; 订单支付效果图&#xff1a; 1. 导入地址簿功能代码&#xff08;单表crud&#xff09; 1.1 需求分析和设计 1.1.1 产品原型&#xff08…

Linux——进程通信(二) 匿名管道的应用:进程池

前言 之前我们学习了进程通过匿名管道进行通信&#xff0c;实现了两个进程的数据传输。 如果我们管理的是很多个进程&#xff0c;通过管道发送指令&#xff0c;因为如果管道中没有数据&#xff0c;读端必须等待&#xff0c;也就是被管理的进程们都在等待我发送的指令&#xf…

DevExpress WinForms crack,DevExpress WinForms组件套件和库

DevExpress WinForms crack,DevExpress WinForms组件套件和库 Reporting & Analytics - Reports, Pivot Tables, PDF Viewer. The DevExpress WinForms Subscription includes royalty-free user interface components for next-gen decision support systems. Whether you…

SegFormer 项目排坑记录

SegFormer 项目排坑记录 任务记录创建conda环境 准备数据库和预训练参数程序配置修改测试可视化训练 任务 需要复现SegFormer分割项目&#xff0c;似乎还有点麻烦&#xff0c;参考这几个进行复现&#xff0c;记录下过程&#xff1a; SegFormer mmsegmentation CSDN博客 知乎博…

保姆级教学!微信小程序设计全攻略!

微信小程序开启了互联网软件的新使用模式。在各种微信小程序争相抢占流量的同时&#xff0c;如何设计微信小程序&#xff1f;让用户感到舒适是设计师在产品设计初期应该考虑的问题。那么如何做好微信小程序的设计呢&#xff1f;即时设计总结了以下设计指南&#xff0c;希望对准…

车企都卷入鸿蒙开发的行了!难怪岗位需求量涨这么快!!!

近日华为鸿蒙 OS 官微宣布&#xff0c;首批汽车行业伙伴广汽传祺、岚图汽车、零跑汽车、凯翼汽车官宣加入鸿蒙生态。 这四家车企加入&#xff0c;会不会影响着其他车企想哪些手机APP厂商那种陆续的跟上呢&#xff1f; 其实在此前国内新能源扛把子比亚迪早已与华为达成了合作&a…

纳米阱可提高未来光学设备的光强度

一种配置光捕获器件的方法有望通过放大光和提高发光纳米材料的发射效率来获得更好的光学纳米器件&#xff0c;而无需复杂的技术升级。 强光束对于从医学到电子产品的无数应用至关重要&#xff0c;但使用日常光源生产它们具有挑战性。然而&#xff0c;它们可以由激光产生。激光…

校园暴力谁之过?如何正确引导孩子行为?

近日&#xff0c;关于河北邯郸校园霸凌事件持续发酵&#xff0c;很多媒体包括央视都是密切关注此事的发展动向&#xff0c;很多媒体人也在持续报道后续事态。那么对于14岁的的少年来说&#xff0c;花样年华的年纪&#xff0c;究竟为什么会犯下如此大的罪行&#xff1f;孩子为什…

电脑msvcp140_1.dll丢失的解决方法,总结5种可靠的方法

在日常使用电脑的过程中&#xff0c;我们可能会遇到一些错误提示&#xff0c;其中之一就是“msvcp1401.dll丢失”。这个DLL文件是Microsoft Visual C Redistributable Package的一部分&#xff0c;对于许多基于Windows的应用程序来说至关重要。这个错误通常会导致某些应用程序无…

系统渐渐沦为“屎山”,这就是真相!

分享是最有效的学习方式。 博客&#xff1a;https://blog.ktdaddy.com/ 背景 小猫维护现有的系统也有一段时间了&#xff0c;踩坑也不少&#xff0c;事故不少。感兴趣的小伙伴可以了解一下&#xff0c;往期的小猫踩坑记合集。 这天&#xff0c;小猫找到了商城系统的第一任开发…

【数据结构与算法】:非递归实现快速排序、归并排序

&#x1f525;个人主页&#xff1a; Quitecoder &#x1f525;专栏&#xff1a;数据结构与算法 上篇文章我们详细讲解了递归版本的快速排序&#xff0c;本篇我们来探究非递归实现快速排序和归并排序 目录 1.非递归实现快速排序1.1 提取单趟排序1.2 用栈实现的具体思路1.3 代码…

掘根宝典之C++RTTI和类型转换运算符

什么是RTTI RTTI是运行阶段类型识别的简称。 哪些是RTTI? C有3个支持RTTI的元素。 1.dynamic_cast运算符将使用一个指向基类的指针来生成一个指向派生类的指针&#xff0c;否则该运算符返回0——空指针。 2.typeid运算符返回一个指出对象类型的信息 3.type_info结构存储…

【鸿蒙HarmonyOS开发笔记】如何使用图片插帧将低像素图片清晰放大

开发UI时&#xff0c;当我们的原图分辨率较低并且需要放大显示时&#xff0c;图片会模糊并出现锯齿。如下图所示 这时可以使用interpolation()方法对图片进行插值&#xff0c;使图片显示得更清晰。该方法的参数为ImageInterpolation枚举类型&#xff0c;可选的值有: ImageInte…

通过点击按钮实现查看全屏和退出全屏的效果

动态效果如图&#xff1a; 可以通过点击按钮&#xff0c;或者esc键实现全屏和退出全屏的效果 实现代码&#xff1a; <template><div class"hello"><el-button click"fullScreen()" v-if"!isFullscreen">查看全屏</el-butt…

centos创建并运行一个redis容器 并支持数据持久化

步骤 : 创建redis容器命令 docker run --name mr -p 6379:6379 -d redis redis-server --appendonly yes 进入容器 : docker exec -it mr bash 链接redis : redis-cli 查看数据 : keys * 存入一个数据 : set num 666 获取数据 : get num 退出客户端 : exit 再退…

猫头虎分享已解决Bug || TypeError: Cannot interpret ‘float‘ value as integer.

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

luceda ipkiss教程 62:等长波导布线(二)

教程 27介绍了两段波导等长布线的例子&#xff0c;下面同样是通过控制偏移量实现三段波导的等长布线&#xff1a; 所有代码如下&#xff1a; from si_fab import all as pdk from ipkiss3 import all as i3class demo(i3.Circuit):mmi i3.ChildCellProperty(doc"mmi in…

数据导入--Insert into

Insert Into是我们在MySQL中常用的导入方式&#xff0c;StarRocks同样也支持使用Insert into的方式进行数据导入&#xff0c;并且每次insert into操作都是一次完整的导入事务。 在StarRocks中&#xff0c;Insert的语法和MySQL等数据库的语法类似&#xff0c;具体可以参考官网文…