以太坊黄皮书详解(三)

2018-06-12

六、虚拟机的执行模型

上文三四五部分都是从流程上对交易执行(包括合约创建和合约调用)进行了介绍。本章节介绍以太坊中的虚拟机执行的流程。以太坊虚拟机EVM是图灵完备虚拟机器。EVM存在而典型图灵完备机器不存在的唯一限制就是EVM本质上是被gas束缚。因此,可以完成的计算总量本质上是被提供的gas总量限制的。

6.1 基本模型

  • EVM是基于栈(先进后出)的架构。EVM中每个堆栈项的大小为256位,堆栈有一个最大的大小,为1024位。
  • EVM有内存,项目按照可寻址字节数组来存储。内存是易失性的,也就是数据是不持久的。
  • EVM也有一个存储器。不像内存,存储器是非易失性的,并作为系统状态的一部分进行维护。EVM分开保存程序代码,在虚拟ROM 中只能通过特殊指令来访问。
  • EVM同样有属于它自己的语言:“EVM字节码”,在以太坊上运行的智能合约时,通常都是用高级语言例如Solidity来编写代码。然后将它编译成EVM可以理解的EVM字节码。
// EVM is the Ethereum Virtual Machine base object and provides
// the necessary tools to run a contract on the given state with
// the provided context. It should be noted that any error
// generated through any of the calls should be considered a
// revert-state-and-consume-all-gas operation, no checks on
// specific errors should ever be performed. The interpreter makes
// sure that any errors generated are to be considered faulty code.
//
// The EVM should never be reused and is not thread safe.
type EVM struct {
	// Context provides auxiliary blockchain related information
	Context
	// StateDB gives access to the underlying state
	StateDB StateDB
	// Depth is the current call stack
	depth int

	// chainConfig contains information about the current chain
	chainConfig *params.ChainConfig
	// chain rules contains the chain rules for the current epoch
	chainRules params.Rules
	// virtual machine configuration options used to initialise the
	// evm.
	vmConfig Config
	// global (to this context) ethereum virtual machine
	// used throughout the execution of the tx.
	interpreter *Interpreter
	// abort is used to abort the EVM calling operations
	// NOTE: must be set atomically
	abort int32
	// callGasTemp holds the gas available for the current call. This is needed because the
	// available gas is calculated in gasCall* according to the 63/64 rule and later
	// applied in opCall*.
	callGasTemp uint64
}

// Interpreter is used to run Ethereum based contracts and will utilise the
// passed environment to query external sources for state information.
// The Interpreter will run the byte code VM based on the passed
// configuration.
type Interpreter struct {
	evm      *EVM
	cfg      Config
	gasTable params.GasTable
	intPool  *intPool  //栈

	readOnly   bool   // Whether to throw on stateful modifications
	returnData []byte // Last CALL's return data for subsequent reuse
}

6.2 费用

以太坊虚拟机执行过程中,主要有3类费用。

  • 执行过程中的运算费用。
  • 创建或者调用其他合约消耗的费用。
  • 新增的存储的费用。

6.3 运行环境

合约执行过程中的运行环境包括:系统状态$\sigma$,可用gas值g,以及其他包含在I中的一些值,在第四和第五部分也都有涉及到。

  • $I_a$为合约执行的当前账户。对于合约创建则为新创建的合约账户,如果是合约调用,则为接收者账户。
  • $I_o$为原始调用者,即该条交易的发送者。
  • $I_p$为gas价格。
  • $I_d$为合约执行的输入数据。
  • $I_s$为合约当前调用者,如果是条简单的交易,则为交易的发送者。
  • $I_v$为value,单位为Wei,即要转移给当前账户的以太币。
  • $I_b$需要被执行的机器码。合约创建的时候该处即为初始化合约的字节码。
  • $I_H$当前区块的header。
  • $I_e$当前的栈深度。
  • $I_w$相关权限。

执行模型可以用公式121表示。其中子状态如公式122所示。(该处子状态定义和之前是相同的)

6.4 执行过程

  • 执行刚开始时,内存和堆栈都是空的,程序计数器为0。
  • 然后EVM开始递归的执行交易,为每个循环计算系统状态和机器状态。系统状态也就是以太坊的全局状态(global state)。机器状态包含:可获取的gas,程序计数器,内存的内容,内存中字的活跃数,堆栈的内容。
  • 堆栈中的项从栈顶被删除(POP)或者添加(PUSH)。
  • 每个循环,剩余的gas都会被减少相应的量,程序计数器也会增加。

在每个循环的结束,都有四种可能性:

  • 机器到达异常状态(err != nil 的情况。例如 gas不足,无效指令,堆栈项不足,堆栈项会溢出1024,无效的JUMP/JUMPI目的地等等)因此停止,并丢弃任何的更改进入后续处理下一个循环。
  • 机器到达了受控停止(到达执行过程的终点,halts或者revert),整个结束了,机器就会产生一个合成状态,执行之后的剩余gas、产生的子状态、以及组合输出。
  • 假设执行没有遇到异常状态,继续循环执行下一步。
// Run loops and evaluates the contract's code with the given input data and returns
// the return byte-slice and an error if one occurred.
//
// It's important to note that any errors returned by the interpreter should be
// considered a revert-and-consume-all-gas operation except for
// errExecutionReverted which means revert-and-keep-gas-left.
func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err error) {
	// Increment the call depth which is restricted to 1024
	in.evm.depth++
	defer func() { in.evm.depth-- }()

	// Reset the previous call's return data. It's unimportant to preserve the old buffer
	// as every returning call will return new data anyway.
	in.returnData = nil

	// Don't bother with the execution if there's no code.
	if len(contract.Code) == 0 {
		return nil, nil
	}

	//机器状态包含:可获取的gas,程序计数器pc,内存的内容mem,内存中字的活跃数,堆栈的内容stack。
	var (
		op    OpCode        // current opcode
		mem   = NewMemory() // bound memory
		stack = newstack()  // local stack
		// For optimisation reason we're using uint64 as the program counter.
		// It's theoretically possible to go above 2^64. The YP defines the PC
		// to be uint256. Practically much less so feasible.
		pc   = uint64(0) // program counter
		cost uint64
		// copies used by tracer
		pcCopy  uint64 // needed for the deferred Tracer
		gasCopy uint64 // for Tracer to log gas remaining before execution
		logged  bool   // deferred Tracer should ignore already logged steps
	)
	contract.Input = input

	if in.cfg.Debug {
		defer func() {
			if err != nil {
				if !logged {
					in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err)
				} else {
					in.cfg.Tracer.CaptureFault(in.evm, pcCopy, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err)
				}
			}
		}()
	}
	// 循环直到三种结束状态中的一种。
	// 1.异常情况,各种不合法的情况出现时,或者执行出错,会直接return
	// 2.执行中返回REVERT错误。
	// 3.执行中中断。 
	// The Interpreter main run loop (contextual). This loop runs until either an
	// explicit STOP, RETURN or SELFDESTRUCT is executed, an error occurred during
	// the execution of one of the operations or until the done flag is set by the
	// parent context.
	for atomic.LoadInt32(&in.evm.abort) == 0 {
		if in.cfg.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.cfg.JumpTable[op]
		if !operation.valid {
			return nil, fmt.Errorf("invalid opcode 0x%x", int(op))
		}
		// 确保目前栈满足操作要求,比方说有些操作是二元的,那么栈中至少要有两个数据。
		if err := operation.validateStack(stack); err != nil {
			return nil, err
		}
		// If the operation is valid, enforce and write restrictions
		if err := in.enforceRestrictions(op, operation, stack); err != nil {
			return nil, err
		}

		var memorySize uint64
		// calculate the new memory size and expand the memory to fit
		// the operation
		// 计算新操作需要的内存空间
		if operation.memorySize != nil {
			memSize, overflow := bigUint64(operation.memorySize(stack))
			if overflow {
				return nil, errGasUintOverflow
			}
			// memory is expanded in words of 32 bytes. Gas
			// is also calculated in words.
			if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
				return nil, errGasUintOverflow
			}
		}
		// consume the gas and return an error if not enough gas is available.
		// cost is explicitly set so that the capture state defer method can get the proper cost
		// 消耗gas
		cost, err = operation.gasCost(in.gasTable, in.evm, contract, stack, mem, memorySize)
		if err != nil || !contract.UseGas(cost) {
			return nil, ErrOutOfGas
		}
		if memorySize > 0 {
			mem.Resize(memorySize)
		}

		if in.cfg.Debug {
			in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err)
			logged = true
		}

		// execute the operation
		// 执行具体的操作,比方说加法,就是从栈中取出两个值后相加
		res, err := operation.execute(&pc, in.evm, contract, mem, stack)
		// verifyPool is a build flag. Pool verification makes sure the integrity
		// of the integer pool by comparing values to a default value.
		if verifyPool {
			verifyIntegerPool(in.intPool)
		}
		// if the operation clears the return data (e.g. it has returning data)
		// set the last return to the result of the operation.
		if operation.returns {
			in.returnData = res
		}

		switch {
		case err != nil:
			return nil, err
		case operation.reverts:
			return res, errExecutionReverted
		case operation.halts:
			return res, nil
		case !operation.jumps:
			pc++ //计数器加一,接着执行
		}
	}
	return nil, nil
}

6.4.1 整体流程的形式化表示

虚拟机行执行合约的整体流程可以看作是输入系统状态$\sigma$,可用gas值g,输入项I,可以获取到的账户集合T。经过虚拟机函数$\Xi$后,得到新的系统状态$\sigma’$,执行剩余可用gas$\mu_g’$,交易子状态A和组合输出$\mathbf{o}$。如公式123所示。

执行的过程是一个迭代执行的过程,定义X为该迭代函数。如公式124所示。

其中机器状态$\mu$,包括:

  • 可用gas值g,初始值即为输入的可用gas值,公式125。
  • 程序计数器pc,初始值为0,公式126。
  • 内存内容m,初始为全0,公式127。
  • 内存内容中活跃的字数i,初始为0,公式128。
  • 栈s,初始为空的栈,公式129。
  • 组合输出,初始为空,公式130.

迭代函数X定义如公式131-135所示。

  • 如果迭代过程中遇到异常,即$Z(\boldsymbol{\sigma}, \boldsymbol{\mu}, I)$,异常的具体情况见下一小节。则迭代结束,返回空的状态集$\varnothing$,原始机器状态$\boldsymbol{\mu}$,空的子状态$A^0$,空的组合输出$\varnothing$。
  • 如果执行中隐式中断,当前要执行的操作为revert,则迭代结束,返回空的状态集$\varnothing$,临时机器状态$\boldsymbol{\mu’}$(扣除了gas消耗公式134-135),空的子状态$A^0$,组合输出$\mathbf{o}$。
  • 如果执行过程正常,且达到了受控停止的状态,即组合输出$\mathbf{o}$不为空,则返回执行结果以及组合输出。组合输出由$H(\boldsymbol{\mu}, I)$确定公式132,详解见后文6.4.4。
  • 如果执行过程正常,且组合输出为空,继续进行下一轮迭代。

当前要被执行的操作定义如公式136.

  • 当程序计数器小于虚拟机字节码时,当前要执行的操作为字节码中对应操作。
  • 否则为STOP

6.4.2异常的形式化定义

虚拟机执行过程中的异常有以下几种情况,如公式137:

  • gas值不足以支付花费。即$\boldsymbol{\mu}_g < C(\boldsymbol{\sigma}, \boldsymbol{\mu}, I)$。
    func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err error) {
      //...
      for atomic.LoadInt32(&in.evm.abort) == 0 {
          //...
          // gas值不足以支付花费
          cost, err = operation.gasCost(in.gasTable, in.evm, contract, stack, mem, memorySize)
          if err != nil || !contract.UseGas(cost) {
              return nil, ErrOutOfGas
          }
          //...
      }
    }
    
  • 当前的操作是无效的。$\mathbf{\delta}_w = \varnothing$。当前操作w对应的字节码为空。
    func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err error) {
      //...
      for atomic.LoadInt32(&in.evm.abort) == 0 {
          //...
          // 获取当前要执行的操作
          op = contract.GetOp(pc)
          // 确认操作是否在操作集合表中
          operation := in.cfg.JumpTable[op]
          if !operation.valid {
              return nil, fmt.Errorf("invalid opcode 0x%x", int(op))
          }
          //...
      }
    }
    
  • 当前栈不合法(比方说二元操作,栈里只有一个数据)$\lVert\boldsymbol{\mu}_\mathbf{s}\rVert < \mathbf{\delta}_w$
    func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err error) {
      //...
      for atomic.LoadInt32(&in.evm.abort) == 0 {
          //...
          // 确保目前栈满足操作要求,比方说有些操作是二元的,那么栈中至少要有两个数据。
          if err := operation.validateStack(stack); err != nil {
              return nil, err
          }
          // If the operation is valid, enforce and write restrictions
          if err := in.enforceRestrictions(op, operation, stack); err != nil {
              return nil, err
          }
          //...
      }
    }
    
  • 当前操作为跳转,但是跳转到的地方不合法
//跳转操作
func opJump(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
	pos := stack.pop()
	if !contract.jumpdests.has(contract.CodeHash, contract.Code, pos) {
		nop := contract.GetOp(pos.Uint64())
		return nil, fmt.Errorf("invalid jump destination (%v) %v", nop, pos)
	}
	*pc = pos.Uint64()

	evm.interpreter.intPool.put(pos)
	return nil, nil
}

func opJumpi(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
	pos, cond := stack.pop(), stack.pop()
	if cond.Sign() != 0 {
		if !contract.jumpdests.has(contract.CodeHash, contract.Code, pos) {
			nop := contract.GetOp(pos.Uint64())
			return nil, fmt.Errorf("invalid jump destination (%v) %v", nop, pos)
		}
		*pc = pos.Uint64()
	} else {
		*pc++
	}

	evm.interpreter.intPool.put(pos, cond)
	return nil, nil
}
  • 当前操作为返回数据操作,但是返回的数据长度超过了允许长度。
func opReturnDataCopy(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
	var (
		memOffset  = stack.pop()
		dataOffset = stack.pop()
		length     = stack.pop()

		end = evm.interpreter.intPool.get().Add(dataOffset, length)
	)
	defer evm.interpreter.intPool.put(memOffset, dataOffset, length, end)
	//判断返回数据长度是否超过允许长度
	if end.BitLen() > 64 || uint64(len(evm.interpreter.returnData)) < end.Uint64() {
		return nil, errReturnDataOutOfBounds
	}
	memory.Set(memOffset.Uint64(), length.Uint64(), evm.interpreter.returnData[dataOffset.Uint64():end.Uint64()])

	return nil, nil
}

  • 新的栈的大小大于1024,$\lVert\boldsymbol{\mu}_\mathbf{s}\rVert - \mathbf{\delta}_w + \mathbf{\alpha}_w > 1024$
  • 没用权限却尝试非静态调用$\neg I_{\mathrm{w}} \wedge W(w, \boldsymbol{\mu})$。其中非静态调用有,CREATE,SSTORE,SELFDESTRUCT,LOG0,LOG1,LOG2,LOG3,LOG4,以及CALL和CALLCHAINCODE,如公式138。
func (in *Interpreter) enforceRestrictions(op OpCode, operation operation, stack *Stack) error {
	if in.evm.chainRules.IsByzantium {
		if in.readOnly {
			// If the interpreter is operating in readonly mode, make sure no
			// state-modifying operation is performed. The 3rd stack item
			// for a call operation is the value. Transferring value from one
			// account to the others means the state is modified and should also
			// return with an error.
			if operation.writes || (op == CALL && stack.Back(2).BitLen() > 0) {
				return errWriteProtection
			}
		}
	}
	return nil
}

6.4.3 跳转表的形式化定义

公式139-141为对跳转表的定义。

  • 跳转到c,指跳转到c的第0个指令。公式140.
  • 跳转到c的第i个指令,如果i大于等于c的长度,则跳转到空,否则跳转到i的下一个有效指令的位置。公式140.

其中$N(i,w)$指下一个有效的操作指令的位置,因为是找有效的指令,和不是数据,所以遇到push指令的时候需要跳过push进去的数据。

  • 如果当前命令为PUSHX,则跳转到i+X+1(因为栈里接下来全是push进去的数据,寻找指令,要跳过这些数据)。
  • 否则为i + 1.

6.4.4 正常的可控中断的形式化定义

正常的可控中断有以下三种:

  • 当前的操作为RETURN或者REVERT,相应的进行返回。
  • 当前操作为STOP或者SELFDESTRUCT。
  • 其他情况返回空。

注:这里STOP或者SELFDESTRUCT返回的并不是空,所以在6.1中也是o不为空的情况。

6.5 执行的生命周期的形式化表示

执行过程中栈元素从栈顶入栈和出栈(黄皮书中栈顶元素为0),每次从栈顶取出操作执行,并根据操作进行相应的出栈和入栈操作。栈中其余的元素不变。如公式143-146所示。

公式143表示,每次操作原先的状态$\boldsymbol{\sigma}$,原先的机器状态$\boldsymbol{\mu}$,原先的子状态A,原先的参数I,执行后为新临时状态$\boldsymbol{\sigma}’$,新的机器状态$\boldsymbol{\mu}’$,新的子状态A’,参数I不变。

公式144表示,栈的变化,其中为当前操作的入栈集合,$\mathbf{\delta}_{w}$为当前操作的出栈集合。

公式145表示,栈的长度,即为在原先栈的长度+变化的长度。

公式146表示,栈中的元素,除了新增的元素$\mathbf{\alpha}_{w}$,其余的不变。(黄皮书中栈顶是0,所以如果入栈元素多于出栈元素,则原先元素的索引都会加相应偏移)。 公式147-148表示每一次迭代gas和程序计数器pc的变化。其中gas值为原有gas减去当前操作的花费,pc在JUMP和JUMPI的时候是跳转到相应位置,其余情况是加一,到下一条指令。

公式149-152,假设操作对其余的元素都不产生影响。实际上不同操作会对不同部分有影响。

七、区块的最终确定

在区块的生成过程中,更多的时候是一棵树状的结构。为了在树形结构中确认一条链作为区块链,在以太坊中定义“最重”的链为主链。 这里的重指的是累计难度(td,total difficulty定义如公式153-154),也就是td最大的链为主链。 公式153-154表示当前区块的累积难度td值为其父区块的累积难度td值加上当前区块的难度值。

区块的最终确定涉及到四个阶段:

  • 校验ommers。
  • 校验交易。
  • 计算和提交奖励。
  • 验证世界状态和区块的nonce值。
// ValidateBody validates the given block's uncles and verifies the the block
// header's transaction and uncle roots. The headers are assumed to be already
// validated at this point.
func (v *BlockValidator) ValidateBody(block *types.Block) error {
	// Check whether the block's known, and if not, that it's linkable
	if v.bc.HasBlockAndState(block.Hash(), block.NumberU64()) {
		return ErrKnownBlock
	}
	// 验证父区块是否存在
	if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
		if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) {
			return consensus.ErrUnknownAncestor
		}
		return consensus.ErrPrunedAncestor
	}
	// 验证ommers
	// Header validity is known at this point, check the uncles and transactions
	header := block.Header()
	if err := v.engine.VerifyUncles(v.bc, block); err != nil {
		return err
	}
	if hash := types.CalcUncleHash(block.Uncles()); hash != header.UncleHash {
		return fmt.Errorf("uncle root hash mismatch: have %x, want %x", hash, header.UncleHash)
	}
	// 验证交易的hash值
	if hash := types.DeriveSha(block.Transactions()); hash != header.TxHash {
		return fmt.Errorf("transaction root hash mismatch: have %x, want %x", hash, header.TxHash)
	}
	return nil
}

7.1 Ommer的校验

Ommer或者说uncle是指父区块的兄弟区块,以太坊中block的uncles字段用以存储uncle区块,在区块的最终确定的时候需要对ommer进行校验。

// VerifyUncles verifies that the given block's uncles conform to the consensus
// rules of the stock Ethereum ethash engine.
func (ethash *Ethash) VerifyUncles(chain consensus.ChainReader, block *types.Block) error {
	// If we're running a full engine faking, accept any input as valid
	if ethash.config.PowMode == ModeFullFake {
		return nil
	}
	// 每个区块最多有两个uncle区块
	// Verify that there are at most 2 uncles included in this block
	if len(block.Uncles()) > maxUncles {
		return errTooManyUncles
	}
	// Gather the set of past uncles and ancestors
	uncles, ancestors := set.New(), make(map[common.Hash]*types.Header)

	// 统计七代以内的区块
	number, parent := block.NumberU64()-1, block.ParentHash()
	for i := 0; i < 7; i++ {
		ancestor := chain.GetBlock(parent, number)
		if ancestor == nil {
			break
		}
		ancestors[ancestor.Hash()] = ancestor.Header()
		for _, uncle := range ancestor.Uncles() {
			uncles.Add(uncle.Hash())
		}
		parent, number = ancestor.ParentHash(), number-1
	}
	ancestors[block.Hash()] = block.Header()
	uncles.Add(block.Hash())

	// 确认该节点
	// Verify each of the uncles that it's recent, but not an ancestor
	for _, uncle := range block.Uncles() {
		// Make sure every uncle is rewarded only once
		hash := uncle.Hash()
		if uncles.Has(hash) {
			return errDuplicateUncle
		}
		uncles.Add(hash)

		// Make sure the uncle has a valid ancestry
		// 确认uncle区块是确实是uncle区块
		if ancestors[hash] != nil {
			return errUncleIsAncestor
		}
		if ancestors[uncle.ParentHash] == nil || uncle.ParentHash == block.ParentHash() {
			return errDanglingUncle
		}
		// 验证uncle区块的header
		if err := ethash.verifyHeader(chain, uncle, ancestors[uncle.ParentHash], true, true); err != nil {
			return err
		}
	}
	return nil
}

在以太坊中一个区块的

  • 兄弟区块$s(U, H)$定义如公式157,兄弟区块是指与区块自身有不同,且父区块相同$(P(H) = P(U) \wedge H \neq U $;并且该兄弟区块不在自己的uncles区块中$U \notin B(H)_{\mathbf{U}}$
  • U是否是H的n代以内uncle定义如公式156,若n为0,则返回false(没有0代的概念),否则要么U是H的兄弟区块(一代),要么U是H的父区块的n-1代叔区块。
  • uncle区块数目不超过2个,$\lVert B_{\mathbf{U}} \rVert \leqslant 2$,公式155
  • uncle区块为7代以内的区块,(公式里为父区块的6代以内,即自己的兄弟区块是不会包含在内的)

7.2 校验交易

  • 校验block中的txHash是否为block.Transaction的hash。见本章开始部分代码中ValidateBody的最后部分。
  • 虚拟机逐条执行交易,执行后校验所用的gas值是否和区块的已用gas值相同,见公式158.
  • 校验虚拟机执行后的bloom以及receipt是否与区块头中的bloom以及receiptHash相同。
  • 校验虚拟机执行之后的世界状态的树的根是否与区块头中的Root相同。
// ValidateState validates the various changes that happen after a state
// transition, such as amount of used gas, the receipt roots and the state root
// itself. ValidateState returns a database batch if the validation was a success
// otherwise nil and an error is returned.
func (v *BlockValidator) ValidateState(block, parent *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64) error {
	header := block.Header()
	// 校验交易的usedGas是否为区块的GasUsed
	if block.GasUsed() != usedGas {
		return fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas)
	}
	// Validate the received block's bloom with the one derived from the generated receipts.
	// For valid blocks this should always validate to true.
	rbloom := types.CreateBloom(receipts)
	if rbloom != header.Bloom {
		return fmt.Errorf("invalid bloom (remote: %x  local: %x)", header.Bloom, rbloom)
	}

	// 校验收款的hash值
	// Tre receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, R1]]))
	receiptSha := types.DeriveSha(receipts)
	if receiptSha != header.ReceiptHash {
		return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha)
	}
	// 校验state
	// Validate the state root against the received state root and throw
	// an error if they don't match.
	if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root {
		return fmt.Errorf("invalid merkle root (remote: %x local: %x)", header.Root, root)
	}
	return nil
}

7.3 计算奖励

  • 矿工的奖励为固定奖励$R_{block}$如公式164,如果区块uncles不为空,则每个uncle会带来额外32分之一的$R_{block}$奖励。如公式160,矿工的余额为原余额加上奖励。
  • 对于该区块中的uncles,每个uncle块,计算其矿工的奖励,如公式163,是根据uncle块与当前块的代差来计算的。例如差1代,uncle块为当前块父区块的兄弟,那么该uncle块的矿工可以拿到八分之七的$R_{block}$奖励。差的代数越多,拿到的奖励越少。
// AccumulateRewards credits the coinbase of the given block with the mining
// reward. The total reward consists of the static block reward and rewards for
// included uncles. The coinbase of each uncle block is also rewarded.
func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) {
	// Select the correct block reward based on chain progression
	blockReward := FrontierBlockReward
	if config.IsByzantium(header.Number) {
		blockReward = ByzantiumBlockReward
	}
	// Accumulate the rewards for the miner and any included uncles
	reward := new(big.Int).Set(blockReward)
	// 计算uncle块中矿工的奖励
	r := new(big.Int)
	for _, uncle := range uncles {
		r.Add(uncle.Number, big8)
		r.Sub(r, header.Number)
		r.Mul(r, blockReward)
		r.Div(r, big8)
		state.AddBalance(uncle.Coinbase, r)

		r.Div(blockReward, big32)
		reward.Add(reward, r)
	}

	// 当前矿工的奖励为固定奖励加上跟uncle区块数量相关的一部分奖励
	state.AddBalance(header.Coinbase, reward)
}

7.4 校验state和nonce

  • 区块执行前的世界状态定义为$\Gamma(B)$,如公式165,即执行Process之前的世界状态。
  • 执行即对区块的每条交易,运行交易转换函数(虚拟机执行,ApplyTrasaction)如公式170,每条交易执行完后,走区块的转换函数(Finalize,增加矿工奖励等),如公式169和174所示。
  • 执行交易过程中会产生交易的收据证明,相关内容如公式171-173所示。
  • 执行后的区块与最终区块的主要区别为区块头中的nonce值,mixHash值,如公式166-168所示。
    // Process processes the state changes according to the Ethereum rules by running
    // the transaction messages using the statedb and applying any rewards to both
    // the processor (coinbase) and any included uncles.
    //
    // Process returns the receipts and logs accumulated during the process and
    // returns the amount of gas that was used in the process. If any of the
    // transactions failed to execute due to insufficient gas it will return an error.
    func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) {
      var (
          receipts types.Receipts
          usedGas  = new(uint64)
          header   = block.Header()
          allLogs  []*types.Log
          gp       = new(GasPool).AddGas(block.GasLimit())
      )
      // Mutate the the block and state according to any hard-fork specs
      if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 {
          misc.ApplyDAOHardFork(statedb)
      }
      // Iterate over and process the individual transactions
      for i, tx := range block.Transactions() {
          // 虚拟机执行每条交易
          statedb.Prepare(tx.Hash(), block.Hash(), i)
          receipt, _, err := ApplyTransaction(p.config, p.bc, nil, gp, statedb, header, tx, usedGas, cfg)
          if err != nil {
              return nil, nil, 0, err
          }
          // 得到交易的收据与日志
          receipts = append(receipts, receipt)
          allLogs = append(allLogs, receipt.Logs...)
      }
      // Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
      p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles(), receipts)
    
      return receipts, allLogs, *usedGas, nil
    }
    

公式165定义区块执行前的世界状态为$\Gamma(B)$,如果父区块为空,则世界状态为创世块状态,否则执行前的世界状态为其父区块头对应的世界状态树。 公式166-169定义了区块的世界状态为在执行了转换函数之后${\Pi}$,其nonce值和mixHash值需要满足PoW即难度需求。

公式170定义了第n条交易时的世界状态。当n小于0时,世界状态如前定义为${\Gamma}(B)$,否则第n条交易时的世界状态为在第n-1条交易的基础上应用交易转变函数(执行交易)。

分别表示对应的第n条交易的状态码,日志,以及累积消耗的gas值。

  • 的计算如公式171所示,为第n-1个的累积消耗, 加上当前交易的消耗
  • $\mathbf{R}[n]_{\mathrm{l}}$为在原先的日志基础上增加相应的交易日志。
  • $\mathbf{R}[n]_{\mathrm{z}}$为在原先的状态码列表上增加相应的状态码。

公式174表示最终的区块为在最后交易后的世界状态上$\ell(\boldsymbol{\sigma})$,使用完善区块的转化函数${\Omega}$(即本章的内容,计算奖励等等)