以太坊虚拟机模型全解——概念,指令以及代码

目录

  • 介绍
    • 区块链
    • 世界状态
    • 账户
    • 交易
    • 消息(Message)
    • 去中心化数据库
    • 原子性和顺序
  • 虚拟机
    • 以太坊虚拟机
    • 消息调用
    • 异常
    • Gas和费用
    • 输入和输出
    • 字节指令
    • 指令集
    • 杂项
  • 附录A: 实现
    • Geth源码
    • EVM开发应用
    • solidity ABI
  • 附录:用户界面
    • Geth, mist, Solc, Remix, Truffle
  • 参考

介绍

区块链

在这里插入图片描述

  • 区块链系统实际上可以看做一个基于交易的状态机。
  • 但是区块链不被称作状态链,是因为只需要有区块就能够推出每一个状态。
  • 所以从区块链系统实现的角度来看,称其为区块链更加合适。

世界状态

在这里插入图片描述

  • 世界状态中简化的存储的结构就是账户不同的账户地址和不同的账户状态。
  • 这些内容就能够很好的构建整一个区块链体系。

账户

在这里插入图片描述

  • 账户状态中是可以存储EVM代码的
  • 账户又分为两种,一种是Externally owned account(EOA)(个人拥有账户),一种是Contract account(合约账户)。
  • 只有合约账户中存放着EVM codeStorage两个组件。
    在这里插入图片描述
  • EOA由私钥控制,但是EOA不包含EVM代码。
  • 合约包含EVM代码,并且由合约代码控制。

在这里插入图片描述

  • 账户的地址来源如下图所示
    在这里插入图片描述

交易

  • 交易是一个经过加密签名的指令。
  • 交易的行为都是又一个独有的个人和实体发起的。
  • 交易有两种类别,一种是contract creation,另一种是Message call
    在这里插入图片描述
  • 在创建一个合约的时候,我们实际上执行了两步操作:
    • 创建了一个地址N和一个合约状态N。
    • 然后执行了合约的代码,更新了合约中的storage。
  • 一个交易的结构如下图所示
    在这里插入图片描述

信息(Message)

  • 信息是在两个不同的账户之间传递的内容。
  • 信息是一组数据(字节)和价值(一台)。

在这里插入图片描述

  • 消息的传递总共只有四种情况,它们分别是
    在这里插入图片描述

去中心化数据库

  • 所有人都拥有着世界状态一个数据库,整个区块链是所有人共同拥有的,面向交易型的数据库。
  • 分布式的节点构成了以太坊P2P的网络。
  • 个人通过Web3 API来修改以太坊的世界状态。

原子性和顺序

  • 一个交易是一个原子性的交易,它无法再分或者被破坏。
  • 所以交易符合All or nothing规则,即交易要不成立,要不就不存在。
  • 交易是无法被覆盖的,这说明交易必须顺序性的执行。
  • 同时,交易的顺序是无法被保证的
    在这里插入图片描述
  • 矿工可以决定区块交易的顺序:
    在这里插入图片描述
  • 此时,其他的矿工可能由不同的打包顺序,甚至包含着不同的交易数据,这时就得看最快完成PoW的矿工的选择是怎么样的。

虚拟机

以太坊虚拟机

  • 以太坊虚拟机的核心逻辑如下所示
    在这里插入图片描述
  • 以太坊虚拟机是一个智能合约的运行时环境。

以太坊虚拟机架构

在这里插入图片描述

以太坊虚拟机空间构成

在这里插入图片描述

  • 栈内存:1024个元素*256个比特。
  • 内存:字节寻址的线性内存。
  • Storage: 可持久化存储的内存。(256比特的key对应256bit的value)

在这里插入图片描述

  • 所有的操作都在栈中体现;
  • 操作栈的指令有如PUSH/POP/COPY/SWAP等。

内存

在这里插入图片描述

  • 内存是线性的并且可以在字节程度进行寻址。
  • 内存的处理指令有MSTORE/MSTORE8/MLOAD等。
  • 所有的内存的位置都初始化为0。

账户存储空间

在这里插入图片描述

  • 存储空间是一个键值存储结构,他能够映射256-bit words的键值对。
  • 它通过SSTORE/SLOAD指令进行接入。
  • 所有的位置都被初始化为0。

以太坊虚拟机代码

在这里插入图片描述

  • EVM代码是一段字节码,只有EVM可以本地执行。

执行模型

在这里插入图片描述

消息调用(Message call)

  • EVM可以发送消息给其他的用户,其中包括合约用户和EOA。
  • 信息调用的层级不能超过1024层。
    在这里插入图片描述
  • 首先EVMcode操控栈,栈再通过操控call message的指令通过arguments导入到input data,input data通过再操控EVM的内存和栈,生成新的数据,再作为返回值返回给原先的EVM。

异常

  • 以太坊虚拟机中经典的异常有错误地址错误指令燃料不足栈向下溢出等。
    在这里插入图片描述

字节顺序

Endian for Memory(内存端位)

在这里插入图片描述

  • EVM是大端寻址,也即第N个位置对应着第LSB个位置。
  • BYTE指令是从左到右,从MSB出发。
  • SIGNEXTEND指令是从右到左,即从LSB出发。

Push动作的字节顺序

在这里插入图片描述

  • PUSH动作从LSB往MSB打入。

指令集

  • 有基本的256-bit的操作。
  • 合约创建和销毁
    • CREATE, DELEGATECALL
  • Hash
    • SHA3
  • 变换操作
    • 使用MUL或者DIV, SDIV。
  • DIV操作
    • 并没有非0的异常报错。
几种代码复制的方法

在这里插入图片描述

  • 这里面有从input data复制到栈的CALLDATALOAD指令。
  • 又从input data复制到内存的CALLDATACOPY指令。
  • 从EVM code向Memory传送代码的CODECOPY指令。
  • 有从内存复制到下一个虚拟机代码的EXTCODECOPY指令。

来自MUL, DIV和SDIV的移动指令

  • 位向左移动可以用MUL表示。
    • MUL m(2^n)==m<<n
  • 位向右移动可以用DIV或者SDIV表示。
    • DIV m (2^n) == m>>n
    • DIV用于逻辑右移。
    • SDIV用于算数右移。

杂项

  • Solidity源码,Viper源码和LLL源码经过编译之后,都能够得到EVM的源码。
  • eWASM会作为下一代的虚拟机。

一些简单的源码解析

[core/state/statedb.go]

type StateDB struct {
	db           Database
	prefetcher   *triePrefetcher
	originalRoot common.Hash // The pre-state root, before any changes were made
	trie         Trie
	hasher       crypto.KeccakState

	snaps         *snapshot.Tree
	snap          snapshot.Snapshot
	snapDestructs map[common.Hash]struct{}
	snapAccounts  map[common.Hash][]byte
	snapStorage   map[common.Hash]map[common.Hash][]byte

	// This map holds 'live' objects, which will get modified while processing a state transition.
	//↓下面这行存储着整一个账号状态,包括上面的图提到的所有内容
	stateObjects        map[common.Address]*stateObject
	stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie
	stateObjectsDirty   map[common.Address]struct{} // State objects modified in the current execution

[core/state/state_object.go]

type stateObject struct {
	address  common.Address //项目地址
	addrHash common.Hash 
	data     Account // 账户状态
	db       *StateDB
// Account is the Ethereum consensus representation of accounts.
// These objects are stored in the main account trie.
type Account struct {
	Nonce    uint64
	Balance  *big.Int
	Root     common.Hash // merkle root of the storage trie
	CodeHash []byte
}
//EVM 代码
type Code []byte
//...//
//账户存储信息
type Storage map[common.Hash]common.Hash

栈和内存

[core/vm/stack.go]
type Stack struct {
	data []uint256.Int
}

func newstack() *Stack {
	return stackPool.Get().(*Stack)
}
[core/vm/memory.go]
// Memory implements a simple memory model for the ethereum virtual machine.
type Memory struct {
	store       []byte
	lastGasCost uint64
}

// NewMemory returns a new memory model.
func NewMemory() *Memory {
	return &Memory{}
}

指令集操作

[core/vm/instruction.go]
func opAdd(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
	x, y := scope.Stack.pop(), scope.Stack.peek()
	y.Add(&x, y)
	return nil, nil
}
//...//
func opPop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
	scope.Stack.pop()
	return nil, nil
}
//...//
//memory operation
func opMload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
	v := scope.Stack.peek()
	offset := int64(v.Uint64())
	v.SetBytes(scope.Memory.GetPtr(offset, 32))
	return nil, nil
}
//storage operation
func opMstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
	// pop value of the stack
	mStart, val := scope.Stack.pop(), scope.Stack.pop()
	scope.Memory.Set32(mStart.Uint64(), &val)
	return nil, nil
}
//...//
//information flow opration
func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
	stack := scope.Stack
	// Pop gas. The actual gas in interpreter.evm.callGasTemp.
	// We can use this as a temporary value
	temp := stack.pop()
	gas := interpreter.evm.callGasTemp
	// Pop other call parameters.
	addr, value, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop()
	toAddr := common.Address(addr.Bytes20())
	// Get the arguments from the memory.
	args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))

	var bigVal = big0
	//TODO: use uint256.Int instead of converting with toBig()
	// By using big0 here, we save an alloc for the most common case (non-ether-transferring contract calls),
	// but it would make more sense to extend the usage of uint256.Int
	if !value.IsZero() {
		gas += params.CallStipend
		bigVal = value.ToBig()
	}

	ret, returnGas, err := interpreter.evm.Call(scope.Contract, toAddr, args, gas, bigVal)

	if err != nil {
		temp.Clear()
	} else {
		temp.SetOne()
	}
	stack.push(&temp)
	if err == nil || err == ErrExecutionReverted {
		scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
	}
	scope.Contract.Gas += returnGas

	return ret, nil
}

创建EVM和进行交易

[core/state_processor.go]
func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) {
	msg, err := tx.AsMessage(types.MakeSigner(config, header.Number))
	if err != nil {
		return nil, err
	}
	// Create a new context to be used in the EVM environment
	blockContext := NewEVMBlockContext(header, bc, author)
	vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg)
	return applyTransaction(msg, config, bc, author, gp, statedb, header, tx, usedGas, vmenv)
}

源码调用层级

在这里插入图片描述

参考文档

  • 以太坊黄皮书: https://ethereum.github.io/yellowpaper/paper.pdf
  • 以太坊开发教程:https://github.com/ethereum/wiki/wiki/Ethereum-Development-Tutorial
  • 以太坊虚拟机阐述:https://github.com/takenobu-hs/ethereum-evm-illustrated
  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值