以太坊源碼深入分析(9)-- 以太坊通過EVM執(zhí)行交易過程分析

上一節(jié)分析了同步一個新的區(qū)塊準備插入本地BlockChain之前需要重放并執(zhí)行新區(qū)塊的所有交易咪橙,并產生交易收據(jù)和日志夕膀。以太坊是如何執(zhí)行這些交易呢?這就要請出大名鼎鼎的以太坊虛擬機美侦。
以太坊虛擬機在執(zhí)行交易分為兩個部分产舞,第一部分是創(chuàng)建EVM,計算交易金額菠剩,設置交易對象易猫,計算交易gas花銷;第二部分是EVM 的虛擬機解析器通過合約指令具壮,執(zhí)行智能合約代碼准颓,具體來看看源碼。

一棺妓,創(chuàng)建EVM攘已,通過EVM執(zhí)行交易流程
上一節(jié)分析BlockChain調用processor.Process()遍歷block的所有交易,然后調用:

receipt, _, err := ApplyTransaction(p.config, p.bc, nil, gp, statedb, header, tx, usedGas, cfg)怜跑。

執(zhí)行交易并返回收據(jù)數(shù)據(jù)

func ApplyTransaction(config *params.ChainConfig, bc *BlockChain, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, uint64, error) {
    msg, err := tx.AsMessage(types.MakeSigner(config, header.Number))
    if err != nil {
        return nil, 0, err
    }
    // Create a new context to be used in the EVM environment
    context := NewEVMContext(msg, header, bc, author)
    // Create a new environment which holds all relevant information
    // about the transaction and calling mechanisms.
    vmenv := vm.NewEVM(context, statedb, config, cfg)
    // Apply the transaction to the current state (included in the env)
    _, gas, failed, err := ApplyMessage(vmenv, msg, gp)
    if err != nil {
        return nil, 0, err
    }
    // Update the state with pending changes
    var root []byte
    if config.IsByzantium(header.Number) {
        statedb.Finalise(true)
    } else {
        root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes()
    }
    *usedGas += gas

    // Create a new receipt for the transaction, storing the intermediate root and gas used by the tx
    // based on the eip phase, we're passing wether the root touch-delete accounts.
    receipt := types.NewReceipt(root, failed, *usedGas)
    receipt.TxHash = tx.Hash()
    receipt.GasUsed = gas
    // if the transaction created a contract, store the creation address in the receipt.
    if msg.To() == nil {
        receipt.ContractAddress = crypto.CreateAddress(vmenv.Context.Origin, tx.Nonce())
    }
    // Set the receipt logs and create a bloom for filtering
    receipt.Logs = statedb.GetLogs(tx.Hash())
    receipt.Bloom = types.CreateBloom(types.Receipts{receipt})

    return receipt, gas, err
}

1样勃,首先調用tx.Message()方法產生交易Message。這個方法通過txdata數(shù)據(jù)來拼接Message對象性芬,并通過簽名方法signer.Sender(tx)峡眶,對txdata 的V、R 植锉、S三個數(shù)進行解密得到這個交易的簽名公鑰(也是就是發(fā)送方的地址)辫樱。發(fā)送方的地址在交易數(shù)據(jù)中是沒有的,這主要是為了防止交易數(shù)據(jù)被篡改俊庇,任何交易數(shù)據(jù)的變化后通過signer.Sender方法都不能得到正確的地址狮暑。
2鸡挠,調用 NewEVMContext(msg, header, bc, author)創(chuàng)建EVM的上下文環(huán)境,調用vm.NewEVM(context, statedb, config, cfg)創(chuàng)建EVM對象心例,并在內部創(chuàng)建一個evm.interpreter(虛擬機解析器)宵凌。
3鞋囊,調用ApplyMessage(vmenv, msg, gp)方法通過EVM對象來執(zhí)行Message止后。
重點看看ApplyMessage()方法的實現(xiàn):

func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) ([]byte, uint64, bool, error) {
    return NewStateTransition(evm, msg, gp).TransitionDb()
}

創(chuàng)建stateTransition對象,執(zhí)行TransitionDb()方法:

func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bool, err error) {
    if err = st.preCheck(); err != nil {
        return
    }
    msg := st.msg
    sender := st.from() // err checked in preCheck

    homestead := st.evm.ChainConfig().IsHomestead(st.evm.BlockNumber)
    contractCreation := msg.To() == nil

    // Pay intrinsic gas
    gas, err := IntrinsicGas(st.data, contractCreation, homestead)
    if err != nil {
        return nil, 0, false, err
    }
    if err = st.useGas(gas); err != nil {
        return nil, 0, false, err
    }

    var (
        evm = st.evm
        // vm errors do not effect consensus and are therefor
        // not assigned to err, except for insufficient balance
        // error.
        vmerr error
    )
    if contractCreation {
        ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value)
    } else {
        // Increment the nonce for the next transaction
        st.state.SetNonce(sender.Address(), st.state.GetNonce(sender.Address())+1)
        ret, st.gas, vmerr = evm.Call(sender, st.to().Address(), st.data, st.gas, st.value)
    }
    if vmerr != nil {
        log.Debug("VM returned with error", "err", vmerr)
        // The only possible consensus-error would be if there wasn't
        // sufficient balance to make the transfer happen. The first
        // balance transfer may never fail.
        if vmerr == vm.ErrInsufficientBalance {
            return nil, 0, false, vmerr
        }
    }
    st.refundGas()
    st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))

    return ret, st.gasUsed(), vmerr != nil, err
}

3.1溜腐,調用IntrinsicGas()方法译株,通過計算消息的大小以及是否是合約創(chuàng)建交易,來計算此次交易需消耗的gas挺益。
3.2歉糜,如果是合約創(chuàng)建交易,調用evm.Create(sender, st.data, st.gas, st.value)來執(zhí)行message

func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {

    // Depth check execution. Fail if we're trying to execute above the
    // limit.
    if evm.depth > int(params.CallCreateDepth) {
        return nil, common.Address{}, gas, ErrDepth
    }
    if !evm.CanTransfer(evm.StateDB, caller.Address(), value) {
        return nil, common.Address{}, gas, ErrInsufficientBalance
    }
    // Ensure there's no existing contract already at the designated address
    nonce := evm.StateDB.GetNonce(caller.Address())
    evm.StateDB.SetNonce(caller.Address(), nonce+1)

    contractAddr = crypto.CreateAddress(caller.Address(), nonce)
    contractHash := evm.StateDB.GetCodeHash(contractAddr)
    if evm.StateDB.GetNonce(contractAddr) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) {
        return nil, common.Address{}, 0, ErrContractAddressCollision
    }
    // Create a new account on the state
    snapshot := evm.StateDB.Snapshot()
    evm.StateDB.CreateAccount(contractAddr)
    if evm.ChainConfig().IsEIP158(evm.BlockNumber) {
        evm.StateDB.SetNonce(contractAddr, 1)
    }
    evm.Transfer(evm.StateDB, caller.Address(), contractAddr, value)

    // initialise a new contract and set the code that is to be used by the
    // E The contract is a scoped evmironment for this execution context
    // only.
    contract := NewContract(caller, AccountRef(contractAddr), value, gas)
    contract.SetCallCode(&contractAddr, crypto.Keccak256Hash(code), code)

    if evm.vmConfig.NoRecursion && evm.depth > 0 {
        return nil, contractAddr, gas, nil
    }

    if evm.vmConfig.Debug && evm.depth == 0 {
        evm.vmConfig.Tracer.CaptureStart(caller.Address(), contractAddr, true, code, gas, value)
    }
    start := time.Now()

    ret, err = run(evm, contract, nil)

    // check whether the max code size has been exceeded
    maxCodeSizeExceeded := evm.ChainConfig().IsEIP158(evm.BlockNumber) && len(ret) > params.MaxCodeSize
    // if the contract creation ran successfully and no errors were returned
    // calculate the gas required to store the code. If the code could not
    // be stored due to not enough gas set an error and let it be handled
    // by the error checking condition below.
    if err == nil && !maxCodeSizeExceeded {
        createDataGas := uint64(len(ret)) * params.CreateDataGas
        if contract.UseGas(createDataGas) {
            evm.StateDB.SetCode(contractAddr, ret)
        } else {
            err = ErrCodeStoreOutOfGas
        }
    }

    // When an error was returned by the EVM or when setting the creation code
    // above we revert to the snapshot and consume any gas remaining. Additionally
    // when we're in homestead this also counts for code storage gas errors.
    if maxCodeSizeExceeded || (err != nil && (evm.ChainConfig().IsHomestead(evm.BlockNumber) || err != ErrCodeStoreOutOfGas)) {
        evm.StateDB.RevertToSnapshot(snapshot)
        if err != errExecutionReverted {
            contract.UseGas(contract.Gas)
        }
    }
    // Assign err if contract code size exceeds the max while the err is still empty.
    if maxCodeSizeExceeded && err == nil {
        err = errMaxCodeSizeExceeded
    }
    if evm.vmConfig.Debug && evm.depth == 0 {
        evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
    }
    return ret, contractAddr, contract.Gas, err
}

3.2.1望众,evm執(zhí)行棧深度不能超過1024匪补,發(fā)送方持有的以太坊數(shù)量大于此次合約交易金額。
3.2.2烂翰,對該發(fā)送方地址的nonce值+1夯缺,通過地址和nonce值生成合約地址,通過合約地址得到合約hash值甘耿。
3.2.3踊兜,記錄一個狀態(tài)快照,用來后見失敗回滾佳恬。
3.2.4捏境,為這個合約地址創(chuàng)建一個合約賬戶,并為這個合約賬戶設置nonce值為1
3.2.5毁葱,產生以太坊資產轉移垫言,發(fā)送方地址賬戶金額減value值,合約賬戶的金額加value值倾剿。
3.2.6骏掀,根據(jù)發(fā)送方地址和合約地址,以及金額value 值和gas柱告,合約代碼和代碼hash值截驮,創(chuàng)建一個合約對象
3.2.7,run方法來執(zhí)行合約际度,內部調用evm的解析器來執(zhí)行合約指令葵袭,如果是預編譯好的合約,則預編譯執(zhí)行合約就行乖菱。
3.2.8坡锡,如果執(zhí)行ok蓬网,setcode更新這個合約地址狀態(tài),設置usegas為創(chuàng)建合約的gas鹉勒。如果執(zhí)行出錯帆锋,則回滾到之前快照狀態(tài),設置usegas為傳入的合約gas禽额。

3.3锯厢,如果不是新創(chuàng)建的合約,則調用evm.Call(sender, st.to().Address(), st.data, st.gas, st.value)方法脯倒,同時更新發(fā)送方地址nonce值+1.

func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
    if evm.vmConfig.NoRecursion && evm.depth > 0 {
        return nil, gas, nil
    }

    // Fail if we're trying to execute above the call depth limit
    if evm.depth > int(params.CallCreateDepth) {
        return nil, gas, ErrDepth
    }
    // Fail if we're trying to transfer more than the available balance
    if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
        return nil, gas, ErrInsufficientBalance
    }

    var (
        to       = AccountRef(addr)
        snapshot = evm.StateDB.Snapshot()
    )
    if !evm.StateDB.Exist(addr) {
        precompiles := PrecompiledContractsHomestead
        if evm.ChainConfig().IsByzantium(evm.BlockNumber) {
            precompiles = PrecompiledContractsByzantium
        }
        if precompiles[addr] == nil && evm.ChainConfig().IsEIP158(evm.BlockNumber) && value.Sign() == 0 {
            return nil, gas, nil
        }
        evm.StateDB.CreateAccount(addr)
    }
    evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value)

    // Initialise a new contract and set the code that is to be used by the EVM.
    // The contract is a scoped environment for this execution context only.
    contract := NewContract(caller, to, value, gas)
    contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))

    start := time.Now()

    // Capture the tracer start/end events in debug mode
    if evm.vmConfig.Debug && evm.depth == 0 {
        evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)

        defer func() { // Lazy evaluation of the parameters
            evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
        }()
    }
    ret, err = run(evm, contract, input)

    // When an error was returned by the EVM or when setting the creation code
    // above we revert to the snapshot and consume any gas remaining. Additionally
    // when we're in homestead this also counts for code storage gas errors.
    if err != nil {
        evm.StateDB.RevertToSnapshot(snapshot)
        if err != errExecutionReverted {
            contract.UseGas(contract.Gas)
        }
    }
    return ret, contract.Gas, err
}

evm.call方法和evm.create方法大致相同实辑,我們來說說不一樣的地方。
3.3.1藻丢,call方法調用的是一個存在的合約地址的合約剪撬,所以不用創(chuàng)建合約賬戶。如果call方法發(fā)現(xiàn)本地沒有合約接收方的賬戶悠反,則需要創(chuàng)建一個接收方的賬戶残黑,并更新本地狀態(tài)數(shù)據(jù)庫。
3.3.2斋否,create方法的資金transfer轉移是在創(chuàng)建合約用戶賬戶和這個合約賬戶之間發(fā)生梨水,而call方法的資金轉移是在合約的發(fā)送方和合約的接收方之間產生。

3.4如叼,TransitionDb()方法執(zhí)行完合約冰木,調用st.refundGas()方法計算合約退稅,調用evm SSTORE指令 或者evm SUICIDE指令銷毀合約十都會產生退稅笼恰。
3.5踊沸,計算合約產生的gas總數(shù),加入到礦工賬戶社证,作為礦工收入逼龟。

4,回到最開始的ApplyTransaction()方法追葡,根據(jù)EVM的執(zhí)行結果腺律,拼接交易receipt數(shù)據(jù),其中receipt.Logs日志數(shù)據(jù)是EVM執(zhí)行指令代碼的時候產生的宜肉,receipt.Bloom根據(jù)日志數(shù)據(jù)建立bloom過濾器匀钧。

二,EVM 的虛擬機解析器通過運行合約指令谬返,執(zhí)行智能合約代碼
我們從 3.2.7 執(zhí)行合約的run()方法入手之斯,它調用了evm.interpreter.Run(contract, input)方法

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
    }

    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)
                }
            }
        }()
    }
    // 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
            }
        }

        if !in.cfg.DisableGasMetering {
            // consume the gas and return an error if not enough gas is available.
            // cost is explicitly set so that the capture state defer method cas get the proper cost
            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
}

我們直接看解析器處理的主循環(huán),之前的代碼都是在初始化一些臨時變量遣铝。
1佑刷,首先調用contract.GetOp(pc)從和約二進制數(shù)據(jù)里取得第pc個opcode莉擒,opcode是以太坊虛擬機指令,一共不超過256個瘫絮,正好一個byte大小能裝下涨冀。
2,從解析器的JumpTable表中查到op對應的operation麦萤。比如opcode是SHA3(0x20)鹿鳖,取到的operation就是

    SHA3: {
            execute:       opSha3,
            gasCost:       gasSha3,
            validateStack: makeStackFunc(2, 1),
            memorySize:    memorySha3,
            valid:         true,
        }

execute表示指令對應的執(zhí)行方法
gasCost表示執(zhí)行這個指令需要消耗的gas
validateStack計算是不是解析器棧溢出
memorySize用于計算operation的占用內存大小

3,如果operation可用频鉴,解析器棧不超過1024栓辜,且讀寫不沖突
4恋拍,計算operation的memorysize垛孔,不能大于64位。
5施敢,根據(jù)不同的指令周荐,指令的memorysize等,調用operation.gasCost()方法計算執(zhí)行operation指令需要消耗的gas僵娃。
6概作,調用operation.execute(&pc, in.evm, contract, mem, stack)執(zhí)行指令對應的方法。
7默怨,operation.reverts值是true或者operation.halts值是true的指令讯榕,會跳出主循環(huán),否則繼續(xù)遍歷下個op匙睹。
8愚屁,operation指令集里面有4個特殊的指令LOG0,LOG1痕檬,LOG2霎槐,LOG3,它們的指令執(zhí)行方法makeLog()會產生日志數(shù)據(jù)梦谜,日志內容包括EVM解析棧內容丘跌,指令內存數(shù)據(jù),區(qū)塊信息唁桩,合約信息等闭树。這些日志數(shù)據(jù)會寫入到tx的Receipt的logs里面,并存入本地ldb數(shù)據(jù)庫荒澡。

總結
EVM是以太坊的核心功能报辱,得益于EVM,以太坊把區(qū)塊鏈帶入了2.0時代仰猖,這是一個非常偉大的進步捏肢。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末奈籽,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子鸵赫,更是在濱河造成了極大的恐慌衣屏,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辩棒,死亡現(xiàn)場離奇詭異狼忱,居然都是意外死亡,警方通過查閱死者的電腦和手機一睁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門钻弄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人者吁,你說我怎么就攤上這事窘俺。” “怎么了复凳?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵瘤泪,是天一觀的道長。 經(jīng)常有香客問我育八,道長对途,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任髓棋,我火速辦了婚禮实檀,結果婚禮上,老公的妹妹穿的比我還像新娘按声。我一直安慰自己膳犹,他們只是感情好,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布儒喊。 她就那樣靜靜地躺著镣奋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪怀愧。 梳的紋絲不亂的頭發(fā)上侨颈,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機與錄音芯义,去河邊找鬼哈垢。 笑死,一個胖子當著我的面吹牛扛拨,可吹牛的內容都是我干的耘分。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼求泰!你這毒婦竟也來了央渣?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤渴频,失蹤者是張志新(化名)和其女友劉穎芽丹,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體卜朗,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡拔第,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了场钉。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蚊俺。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖逛万,靈堂內的尸體忽然破棺而出泳猬,到底是詐尸還是另有隱情,我是刑警寧澤泣港,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布暂殖,位于F島的核電站价匠,受9級特大地震影響当纱,放射性物質發(fā)生泄漏。R本人自食惡果不足惜踩窖,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一分预、第九天 我趴在偏房一處隱蔽的房頂上張望俗他。 院中可真熱鬧,春花似錦、人聲如沸涛目。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奶段。三九已至,卻和暖如春伙狐,著一層夾襖步出監(jiān)牢的瞬間涮毫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工贷屎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留罢防,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓唉侄,卻偏偏與公主長得像咒吐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內容

  • 簡介 不管你們知不知道以太坊(Ethereum blockchain)是什么恬叹,但是你們大概都聽說過以太坊候生。最近在新...
    Lilymoana閱讀 3,887評論 1 22
  • 1區(qū)塊 所有的交易都被分組為“區(qū)塊”。區(qū)塊鏈包含一系列鏈接在一 起的這樣的塊绽昼。 在以太坊陶舞,一個區(qū)塊包括: 區(qū)塊頭 ...
    布尼區(qū)塊鏈閱讀 2,214評論 0 2
  • 以太坊(Ethereum ):下一代智能合約和去中心化應用平臺 翻譯:巨蟹 、少平 譯者注:中文讀者可以到以太坊愛...
    車圣閱讀 3,722評論 1 7
  • 今天上班感覺身體不是太舒服但也是把每一步都仔細的做好了雖然中間出了些小毛病但都解決了 晚上出去醫(yī)院打了點滴到現(xiàn)在才...
    京心達侯天祥閱讀 61評論 0 0
  • 那年冬天绪励,我告別了江南小城的親友肿孵,第一次乘坐飛機來到了特區(qū)――深圳。深圳離我生活的小城太遠太遠疏魏,以至于在我心中的印...
    花清香hp閱讀 671評論 4 7