以太坊黄皮书详解(三)

六、虚拟机的执行模型

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

6.1 基本模型

evm.JPEG
  • 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所示。(该处子状态定义和之前是相同的)
\begin{equation} (\boldsymbol{\sigma}', g', A, \mathbf{o}) \equiv \Xi(\boldsymbol{\sigma}, g, I) \tag{121} \end{equation}
\begin{equation} A \equiv (\mathbf{s}, \mathbf{l}, \mathbf{t}, r) \tag{122} \end{equation}

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.

\begin{eqnarray} \Xi(\boldsymbol{\sigma}, g, I, T) & \equiv & (\boldsymbol{\sigma}', \boldsymbol{\mu}'_{\mathrm{g}}, A, \mathbf{o}) \tag{123}\\ (\boldsymbol{\sigma}', \boldsymbol{\mu}', A, ..., \mathbf{o}) & \equiv & X\big((\boldsymbol{\sigma}, \boldsymbol{\mu}, A^0, I)\big) \tag{124}\\ \boldsymbol{\mu}_{\mathrm{g}} & \equiv & g \tag{125}\\ \boldsymbol{\mu}_{\mathrm{pc}} & \equiv & 0 \tag{126}\\ \boldsymbol{\mu}_{\mathbf{m}} & \equiv & (0, 0, ...) \tag{127}\\ \boldsymbol{\mu}_{\mathrm{i}} & \equiv & 0 \tag{128}\\ \boldsymbol{\mu}_{\mathbf{s}} & \equiv & () \tag{129}\\ \boldsymbol{\mu}_{\mathbf{o}} & \equiv & () \tag{130} \end{eqnarray}

\begin{equation} \label{eq:X-def} X\big( (\boldsymbol{\sigma}, \boldsymbol{\mu}, A, I) \big) \equiv \begin{cases} \big(\varnothing, \boldsymbol{\mu}, A^0, I, \varnothing\big) & \text{if} \quad Z(\boldsymbol{\sigma}, \boldsymbol{\mu}, I) \\ \big(\varnothing, \boldsymbol{\mu}', A^0, I, \mathbf{o}\big) & \text{if} \quad w = {\small REVERT} \\ O(\boldsymbol{\sigma}, \boldsymbol{\mu}, A, I) \cdot \mathbf{o} & \text{if} \quad \mathbf{o} \neq \varnothing \\ X\big(O(\boldsymbol{\sigma}, \boldsymbol{\mu}, A, I)\big) & \text{otherwise} \\ \end{cases} \tag{131} \end{equation}

\begin{eqnarray} \mathbf{o} & \equiv & H(\boldsymbol{\mu}, I) \tag{132}\\ (a, b, c, d) \cdot e & \equiv & (a, b, c, d, e) \tag{133}\\ \boldsymbol{\mu}' & \equiv & \boldsymbol{\mu}\ \text{except:} \tag{134}\\ \boldsymbol{\mu}'_{\mathrm{g}} & \equiv & \boldsymbol{\mu}_{\mathrm{g}} - C(\boldsymbol{\sigma}, \boldsymbol{\mu}, I) \tag{135} \end{eqnarray}

迭代函数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

\begin{equation}\label{eq:currentoperation} w \equiv \begin{cases} I_{\mathbf{b}}[\boldsymbol{\mu}_{\mathrm{pc}}] & \text{if} \quad \boldsymbol{\mu}_{\mathrm{pc}} < \lVert I_{\mathbf{b}} \rVert \\ {\small STOP} & \text{otherwise} \end{cases} \tag{136} \end{equation}

6.4.2异常的形式化定义

虚拟机执行过程中的异常有以下几种情况,如公式137:
\begin{equation} Z(\boldsymbol{\sigma}, \boldsymbol{\mu}, I) \equiv \begin{array}[t]{l} \boldsymbol{\mu}_g < C(\boldsymbol{\sigma}, \boldsymbol{\mu}, I) \quad \vee \\ \mathbf{\delta}_w = \varnothing \quad \vee \\ \lVert\boldsymbol{\mu}_\mathbf{s}\rVert < \mathbf{\delta}_w \quad \vee \\ ( w = {\small JUMP} \quad \wedge \quad \boldsymbol{\mu}_\mathbf{s}[0] \notin D(I_\mathbf{b}) ) \quad \vee \\ ( w = {\small JUMPI} \quad \wedge \quad \boldsymbol{\mu}_\mathbf{s}[1] \neq 0 \quad \wedge \quad \boldsymbol{\mu}_\mathbf{s}[0] \notin D(I_\mathbf{b}) ) \quad \vee \\ ( w = {\small RETURNDATACOPY} \quad \wedge \quad \boldsymbol{\mu}_{\mathbf{s}}[1] + \boldsymbol{\mu}_{\mathbf{s}}[2] > \lVert\boldsymbol{\mu}_{\mathbf{o}}\rVert) \quad \vee \\ \lVert\boldsymbol{\mu}_\mathbf{s}\rVert - \mathbf{\delta}_w + \mathbf{\alpha}_w > 1024 \quad \vee \\ (\neg I_{\mathrm{w}} \wedge W(w, \boldsymbol{\mu})) \end{array} \tag{137} \end{equation}

\begin{equation} W(w, \boldsymbol{\mu}) \equiv \begin{array}[t]{l} w \in \{ {\small CREATE}, {\small SSTORE}, {\small SELFDESTRUCT}\} \ \vee \\ {\small LOG0} \le w \wedge w \le {\small LOG4} \quad \vee \\ w \in \{ {\small CALL}, {\small CALLCODE}\} \wedge \boldsymbol{\mu}_{\mathbf{s}}[2] \neq 0 \end{array} \tag{138} \end{equation}

  • 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
        }
        //...
    }
}
  • 当前操作为跳转,但是跳转到的地方不合法
    ( w = {\small JUMP} \wedge \boldsymbol{\mu}_\mathbf{s}[0] \notin D(I_\mathbf{b}) ) (w = {\small JUMPI} \wedge \boldsymbol{\mu}_\mathbf{s}[1] \neq 0 \wedge \boldsymbol{\mu}_\mathbf{s}[0] \notin D(I_\mathbf{b}) )
//跳转操作
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
}
  • 当前操作为返回数据操作,但是返回的数据长度超过了允许长度。( w = {\small RETURNDATACOPY} \wedge \boldsymbol{\mu}_{\mathbf{s}}[1] + \boldsymbol{\mu}_{\mathbf{s}}[2] > \lVert\boldsymbol{\mu}_{\mathbf{o}}\rVert)
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 跳转表的形式化定义

\begin{equation} D(\mathbf{c}) \equiv D_{J}(\mathbf{c}, 0) \tag{139} \end{equation}

\begin{equation} D_{J}(\mathbf{c}, i) \equiv \begin{cases} \{\} & \text{if} \quad i \geqslant |\mathbf{c}| \\ \{ i \} \cup D_{J}(\mathbf{c}, N(i, \mathbf{c}[i])) & \text{if} \quad \mathbf{c}[i] = {\small JUMPDEST} \\ D_{J}(\mathbf{c}, N(i, \mathbf{c}[i])) & \text{otherwise} \\ \end{cases} \tag{140} \end{equation}

\begin{equation} N(i, w) \equiv \begin{cases} i + w - {\small PUSH1} + 2 & \text{if} \quad w \in [{\small PUSH1}, {\small PUSH32}] \\ i + 1 & \text{otherwise} \end{cases} \tag{141} \end{equation}
公式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不为空的情况。

\begin{equation} H(\boldsymbol{\mu}, I) \equiv \begin{cases} H_{ {\tiny RETURN}}(\boldsymbol{\mu}) \ \text{if} \quad w \in \{{\small {RETURN}}, {\small REVERT}\} &\\ () \quad\quad\ \text{if} \quad w \in \{ {\small {STOP}}, {\small {SELFDESTRUCT}} \} &\\ \varnothing \quad\quad\ \text{otherwise}& \end{cases} \tag{142} \end{equation}

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

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

\begin{eqnarray} O\big((\boldsymbol{\sigma}, \boldsymbol{\mu}, A, I)\big) & \equiv & (\boldsymbol{\sigma}', \boldsymbol{\mu}', A', I) \tag{143}\\ \Delta & \equiv & \mathbf{\alpha}_{w} - \mathbf{\delta}_{w} \tag{144}\\ \lVert\boldsymbol{\mu}'_{\mathbf{s}}\rVert & \equiv & \lVert\boldsymbol{\mu}_{\mathbf{s}}\rVert + \Delta \tag{145}\\ \quad \forall x \in [\mathbf{\alpha}_{w}, \lVert\boldsymbol{\mu}'_{\mathbf{s}}\rVert): \boldsymbol{\mu}'_{\mathbf{s}}[x] & \equiv & \boldsymbol{\mu}_{\mathbf{s}}[x-\Delta] \tag{146} \end{eqnarray}
公式143表示,每次操作原先的状态\boldsymbol{\sigma},原先的机器状态\boldsymbol{\mu},原先的子状态A,原先的参数I,执行后为新临时状态\boldsymbol{\sigma}',新的机器状态\boldsymbol{\mu}',新的子状态A',参数I不变。

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

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

公式146表示,栈中的元素,除了新增的元素\mathbf{\alpha}_{w},其余的不变。(黄皮书中栈顶是0,所以如果入栈元素多于出栈元素,则原先元素的索引都会加相应偏移)。
\begin{eqnarray} \quad \boldsymbol{\mu}'_{g} & \equiv & \boldsymbol{\mu}_{g} - C(\boldsymbol{\sigma}, \boldsymbol{\mu}, I) \label{eq:mu_pc} \tag{147} \\ \quad \boldsymbol{\mu}'_{\mathrm{pc}} & \equiv & \begin{cases} {J_{\text{JUMP}}}(\boldsymbol{\mu}) & \text{if} \quad w = {\small JUMP} \\ {J_{\text{JUMPI}}}(\boldsymbol{\mu}) & \text{if} \quad w = {\small JUMPI} \\ N(\boldsymbol{\mu}_{\mathrm{pc}}, w) & \text{otherwise} \end{cases} \tag{148} \end{eqnarray}
公式147-148表示每一次迭代gas和程序计数器pc的变化。其中gas值为原有gas减去当前操作的花费,pc在JUMP和JUMPI的时候是跳转到相应位置,其余情况是加一,到下一条指令。

\begin{eqnarray} \boldsymbol{\mu}'_{\mathbf{m}} & \equiv & \boldsymbol{\mu}_{\mathbf{m}} \tag{149}\\ \boldsymbol{\mu}'_{\mathrm{i}} & \equiv & \boldsymbol{\mu}_{\mathrm{i}} \tag{150}\\ A' & \equiv & A \tag{151}\\ \boldsymbol{\sigma}' & \equiv & \boldsymbol{\sigma} \tag{152} \end{eqnarray}
公式149-152,假设操作对其余的元素都不产生影响。实际上不同操作会对不同部分有影响。

七、区块的最终确定

在区块的生成过程中,更多的时候是一棵树状的结构。为了在树形结构中确认一条链作为区块链,在以太坊中定义“最重”的链为主链。
这里的重指的是累计难度(td,total difficulty定义如公式153-154),也就是td最大的链为主链。
\begin{eqnarray} B_{\mathrm{t}} & \equiv & B'_{\mathrm{t}} + B_{\mathrm{d}} \tag{153}\\ B' & \equiv & P(B_{H}) \tag{154} \end{eqnarray}
公式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
}

\begin{equation} \lVert B_{\mathbf{U}} \rVert \leqslant 2 \bigwedge_{\mathbf{U} \in B_{\mathbf{U}}} {V({\mathbf{U}}})\; \wedge \; k({\mathbf{U}}, P(\mathbf{B}_{\mathbf{H}})_{\mathbf{H}}, 6) \tag{155} \end{equation}

\begin{equation} k(U, H, n) \equiv \begin{cases} false & \text{if} \quad n = 0 \\ s(U, H) \; \vee \; k(U, P(H)_{H}, n - 1) & \text{otherwise} \end{cases} \tag{156} \end{equation}

\begin{equation} s(U, H) \equiv (P(H) = P(U)\; \wedge \; H \neq U \; \wedge \; U \notin B(H)_{\mathbf{U}}) \tag{157} \end{equation}
在以太坊中一个区块的

  • 兄弟区块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代以内的区块,{V({\mathbf{U}}})\; \wedge \; k({\mathbf{U}}, P(\mathbf{B}_{\mathbf{H}})_{\mathbf{H}}, 6)(公式里为父区块的6代以内,即自己的兄弟区块是不会包含在内的)

7.2 校验交易

\begin{equation} {B_{H}}_{\mathrm{g}} = {\ell}({\mathbf{R})_{\mathrm{u}}} \tag{158} \end{equation}

  • 校验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 计算奖励

\begin{eqnarray} \Omega(B, \boldsymbol{\sigma}) & \equiv & \boldsymbol{\sigma}': \boldsymbol{\sigma}' = \boldsymbol{\sigma} \quad \text{except:} \tag{159}\\ \qquad\boldsymbol{\sigma}'[{\mathbf{B}_{H}}_{\mathrm{c}}]_{\mathrm{b}} & = & \boldsymbol{\sigma}[{\mathbf{B}_{H}}_{\mathrm{c}}]_{\mathrm{b}} + \left(1 + \frac{\lVert \mathbf{B}_{\mathbf{U}}\rVert}{32}\right)R_{\mathrm{block}} \tag{160}\\ \qquad\forall_{\mathbf{U} \in \mathbf{B}_{\mathbf{U}}}: \tag{161}\\ \nonumber \boldsymbol{\sigma}'[\mathbf{U}_{\mathrm{c}}] & = & \begin{cases} \varnothing &\text{if}\ \boldsymbol{\sigma}[\mathbf{U}_{\mathrm{c}}] = \varnothing\ \wedge\ R = 0 \\ \mathbf{a}' &\text{otherwise} \end{cases} \\ \mathbf{a}' &\equiv& (\boldsymbol{\sigma}[U_{\mathrm{c}}]_{\mathrm{n}}, \boldsymbol{\sigma}[U_{\mathrm{c}}]_{\mathrm{b}} + R, \boldsymbol{\sigma}[U_{\mathrm{c}}]_{\mathbf{s}}, \boldsymbol{\sigma}[U_{\mathrm{c}}]_{\mathrm{c}}) \tag{162}\\ R & \equiv & \left(1 + \frac{1}{8} (U_{\mathrm{i}} - {B_{H}}_{\mathrm{i}})\right) R_{\mathrm{block}} \tag{163} \end{eqnarray}

\begin{equation} \text{Let} \quad R_{\mathrm{block}} = 3 \times 10^{18} \tag{164} \end{equation}

  • 矿工的奖励为固定奖励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
}

\begin{equation} \Gamma(B) \equiv \begin{cases} \boldsymbol{\sigma}_0 & \text{if} \quad P(B_{H}) = \varnothing \\ \boldsymbol{\sigma}_{\mathrm{i}}: \mathtt{\small {TRIE}}(L_{S}(\boldsymbol{\sigma}_{\mathrm{i}})) = {P(B_{H})_{H}}_{\mathrm{r}} & \text{otherwise} \end{cases} \tag{165} \end{equation}
公式165定义区块执行前的世界状态为\Gamma(B),如果父区块为空,则世界状态为创世块状态,否则执行前的世界状态为其父区块头对应的世界状态树。
\begin{eqnarray} \Phi(B) & \equiv & B': \quad B' = B^* \quad \text{except:} \tag{166}\\ B'_{\mathrm{n}} & = & n: \quad x \leqslant \frac{2^{256}}{H_{\mathrm{d}}} \tag{167}\\ B'_{\mathrm{m}} & = & m \quad \text{with } (x, m) = \mathtt{PoW}(B^*_{\cancel {n}}, n, \mathbf{d}) \tag{168}\\ B^* & \equiv & B \quad \text{except:} \quad {B^*_{\mathrm{r}}} = {r}({\Pi}(\Gamma(B), B)) \tag{169} \end{eqnarray}
公式166-169定义了区块的世界状态为在执行了转换函数之后{\Pi},其nonce值和mixHash值需要满足PoW即难度需求。
\begin{equation} \boldsymbol{\sigma}[n] = \begin{cases} {\Gamma}(B) & \text{if} \quad n < 0 \\ {\Upsilon}(\boldsymbol{\sigma}[n - 1], B_{\mathbf{T}}[n]) & \text{otherwise} \end{cases} \tag{170} \end{equation}

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

\begin{equation} \mathbf{R}[n]_{\mathrm{u}} = \begin{cases} 0 & \text{if} \quad n < 0 \\ \begin{array}[b]{l} \Upsilon^g(\boldsymbol{\sigma}[n - 1], B_{\mathbf{T}}[n]) + \mathbf{R}[n-1]_{\mathrm{u}} \end{array} & \text{otherwise} \end{cases} \tag{171} \end{equation}

\begin{equation} \mathbf{R}[n]_{\mathbf{l}} = \Upsilon^{\mathbf{l}}(\boldsymbol{\sigma}[n - 1], B_{\mathbf{T}}[n]) \tag{172} \end{equation}

\begin{equation} \mathbf{R}[n]_{\mathrm{z}} = \Upsilon^{\mathrm{z}}(\boldsymbol{\sigma}[n - 1], B_{\mathbf{T}}[n]) \tag{173} \end{equation}

\mathbf{R}[n]_{\mathrm{z}},\mathbf{R}[n]_{\mathrm{l}},\mathbf{R}[n]_{\mathrm{u}}分别表示对应的第n条交易的状态码,日志,以及累积消耗的gas值。

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容