美文网首页
以太坊中一笔交易的执行流程

以太坊中一笔交易的执行流程

作者: swapmem | 来源:发表于2019-07-09 15:42 被阅读0次

    go-ethereum 源码 commit hash: 2cd6059e51e823012f756112e6e9b2d2920bab74

    如何表示一个账号

    源码位置: core/state/state_object.go:100

    // 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
    }
    
    • Nonce 每次从这个账号发出的交易都会加1,防止重放攻击
    • Balance 账号的余额
    • Root 如果这是一个普通账户,这个值是空值的hash。如果是一个合约账号,那么这个哈希值就是这个合约代码里面用到的存储组成的MPT树的跟哈希
    • CodeHash 如果是智能合约账号,那么通过这个哈希就可以在数据库中直接找到智能合约的字节码

    源码位置: core/state/state_object.go:65

    // stateObject represents an Ethereum account which is being modified.
    //
    // The usage pattern is as follows:
    // First you need to obtain a state object.
    // Account values can be accessed and modified through the object.
    // Finally, call CommitTrie to write the modified storage trie into a database.
    type stateObject struct {
        address  common.Address
        addrHash common.Hash // hash of ethereum address of the account
        data     Account
        db       *StateDB
    
        // DB error.
        // State objects are used by the consensus core and VM which are
        // unable to deal with database-level errors. Any error that occurs
        // during a database read is memoized here and will eventually be returned
        // by StateDB.Commit.
        dbErr error
    
        // Write caches.
        trie Trie // storage trie, which becomes non-nil on first access
        code Code // contract bytecode, which gets set when code is loaded
    
        originStorage Storage // Storage cache of original entries to dedup rewrites
        dirtyStorage  Storage // Storage entries that need to be flushed to disk
    
        // Cache flags.
        // When an object is marked suicided it will be delete from the trie
        // during the "update" phase of the state transition.
        dirtyCode bool // true if the code was updated
        suicided  bool
        deleted   bool
    }
    

    以太坊世界状态的改变是通过操作 stateObject 进行的。这个结构是世界状态MPT树的操作对象。对账号余额的修改,智能合约状态的改变,回滚等操作都通过这个结构进行。

    以太坊白皮书说“以太坊是一个基于交易的状态机”,状态的改变都是通过区块中的每一笔交易驱动。通过 stateObject 我们就可以快速的找到一个账号的信息。

    一笔交易的内部表示

    源码位置: core/types/transaction.go:46

    type Transaction struct {
        data txdata
        // caches
        hash atomic.Value
        size atomic.Value
        from atomic.Value
    }
    
    type txdata struct {
        AccountNonce uint64          `json:"nonce"    gencodec:"required"`
        Price        *big.Int        `json:"gasPrice" gencodec:"required"`
        GasLimit     uint64          `json:"gas"      gencodec:"required"`
        Recipient    *common.Address `json:"to"       rlp:"nil"` // nil means contract creation
        Amount       *big.Int        `json:"value"    gencodec:"required"`
        Payload      []byte          `json:"input"    gencodec:"required"`
    
        // Signature values
        V *big.Int `json:"v" gencodec:"required"`
        R *big.Int `json:"r" gencodec:"required"`
        S *big.Int `json:"s" gencodec:"required"`
    
    }
    
    • AccountNonce 账号随机数
    • Price 每个gas需要消耗多少以太坊币
    • GasLimit 这个操作最多允许消耗的gas
    • Recipient 转账的地址或者智能合约地址
    • Amount 转账数量
    • Payload 如果是普通账户,这个payload就相当于一个备注功能,如果是合约账户那么这个payload就是在调用合约里面的方法,或者是在创建一个智能合约
    • V, R, S 对交易的签名

    交易进入mempool时的验证

    源码位置: core/tx_pool.go:205

    // TxPool contains all currently known transactions. Transactions
    // enter the pool when they are received from the network or submitted
    // locally. They exit the pool when they are included in the blockchain.
    //
    // The pool separates processable transactions (which can be applied to the
    // current state) and future transactions. Transactions move between those
    // two states over time as they are received and processed.
    type TxPool struct {
        config       TxPoolConfig
        chainconfig  *params.ChainConfig
        chain        blockChain
        gasPrice     *big.Int
        txFeed       event.Feed
        scope        event.SubscriptionScope
        chainHeadCh  chan ChainHeadEvent
        chainHeadSub event.Subscription
        signer       types.Signer
        mu           sync.RWMutex
    
        currentState  *state.StateDB      // Current state in the blockchain head
        pendingState  *state.ManagedState // Pending state tracking virtual nonces
        currentMaxGas uint64              // Current gas limit for transaction caps
    
        locals  *accountSet // Set of local transaction to exempt from eviction rules
        journal *txJournal  // Journal of local transaction to back up to disk
    
        pending map[common.Address]*txList   // All currently processable transactions
        queue   map[common.Address]*txList   // Queued but non-processable transactions
        beats   map[common.Address]time.Time // Last heartbeat from each known account
        all     *txLookup                    // All transactions to allow lookups
        priced  *txPricedList                // All transactions sorted by price
    
        wg sync.WaitGroup // for shutdown sync
    
        homestead bool
    }
    

    一些重要字段的解释:

    • config 关于交易池的一些配置信息,比如: 交易池允许的最大容量、交易池允许的最低gasPrice等信息
    • chainConfig 关于区块的一些全局配置信息
    • gasPrice gas费用
    • chainHeadCh 区块链的head发生了改变,需要通过这个chan通知, head发生改变是因为发生了 chain reorganazition
    • queue 这个队列里面保存了账号不可立即执行的交易,也就是nonce不连续的交易

    对交易的验证:

    // validateTx checks whether a transaction is valid according to the consensus
    // rules and adheres to some heuristic limits of the local node (price and size).
    func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
        // Heuristic limit, reject transactions over 32KB to prevent DOS attacks
        if tx.Size() > 32*1024 {
            return ErrOversizedData
        }
        // Transactions can't be negative. This may never happen using RLP decoded
        // transactions but may occur if you create a transaction using the RPC.
        if tx.Value().Sign() < 0 {
            return ErrNegativeValue
        }
        // Ensure the transaction doesn't exceed the current block limit gas.
        if pool.currentMaxGas < tx.Gas() {
            return ErrGasLimit
        }
        // Make sure the transaction is signed properly
        from, err := types.Sender(pool.signer, tx)
        if err != nil {
            return ErrInvalidSender
        }
        // Drop non-local transactions under our own minimal accepted gas price
        local = local || pool.locals.contains(from) // account may be local even if the transaction arrived from the network
        if !local && pool.gasPrice.Cmp(tx.GasPrice()) > 0 {
            return ErrUnderpriced
        }
        // Ensure the transaction adheres to nonce ordering
        if pool.currentState.GetNonce(from) > tx.Nonce() {
            return ErrNonceTooLow
        }
        // Transactor should have enough funds to cover the costs
        // cost == V + GP * GL
        if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 {
            return ErrInsufficientFunds
        }
        intrGas, err := IntrinsicGas(tx.Data(), tx.To() == nil, pool.homestead)
        if err != nil {
            return err
        }
        if tx.Gas() < intrGas {
            return ErrIntrinsicGas
        }
        return nil
    }
    

    对交易的验证点:

    • 交易的大小不超过 32 KB
    • 转账数量不能为负
    • 交易的gas数量不能超过区块最大允许的gas数量(BGL, block gas limit)
    • 验证交易签名的正确性
    • 验证是否满足交易池的配置信息
    • 验证nonce的正确性
    • 验证账户余额是否足够
    • 验证固有gas消耗满足需求

    交易在虚拟机中执行

    源码位置: core/types/transaction.go:383

    // Message is a fully derived transaction and implements core.Message
    //
    // NOTE: In a future PR this will be removed.
    type Message struct {
        to         *common.Address
        from       common.Address
        nonce      uint64
        amount     *big.Int
        gasLimit   uint64
        gasPrice   *big.Int
        data       []byte
        checkNonce bool
    }
    

    前面提到的Transaction结构在虚拟机中执行之前都会转换为 Message 结构:

    // AsMessage returns the transaction as a core.Message.
    //
    // AsMessage requires a signer to derive the sender.
    //
    // XXX Rename message to something less arbitrary?
    func (tx *Transaction) AsMessage(s Signer) (Message, error) {
        msg := Message{
            nonce:      tx.data.AccountNonce,
            gasLimit:   tx.data.GasLimit,
            gasPrice:   new(big.Int).Set(tx.data.Price),
            to:         tx.data.Recipient,
            amount:     tx.data.Amount,
            data:       tx.data.Payload,
            checkNonce: true,
        }
    
        var err error
        msg.from, err = Sender(s, tx)
        return msg, err
    }
    

    源码core/state_processor.go中的 Process 函数第一个参数 block 表示要在EVM中执行的区块,第二个参数 stateDB 是要修改的世界状态数据库, 第三个参数 cfg 是关于EVM的配置信息。Process 函数对区块中的每一笔交易调用 ApplyTransaction 函数。在这个函数中为每一笔交易创建新的虚拟机执行环境。接下来的关键调用链:

    ApplyTransaction -> ApplyMessage -> NewStateTransition -> TransitionDb

    接下来重点看下 TransitionDb

    // TransitionDb will transition the state by applying the current message and
    // returning the result including the used gas. It returns an error if failed.
    // An error indicates a consensus issue.
    func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bool, err 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(msg.From(), st.state.GetNonce(sender.Address())+1)
            ret, st.gas, vmerr = evm.Call(sender, st.to(), st.data, st.gas, st.value)
        }
        ...
        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
    }
    

    在这里会区别对待普通交易和和创建智能合约的交易。evm.Create 根据参数创建智能合约, evm.Call 根据交易信息在虚拟机中执行这笔交易。重点分析下 evm.Call
    源码位置:core/vm/evm.go:181

    // Call executes the contract associated with the addr with the given input as
    // parameters. It also handles any necessary value transfer required and takes
    // the necessary steps to create accounts and reverses the state in case of an
    // execution error or failed value transfer.
    func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
        ...
        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))
        ...
        ret, err = run(evm, contract, input, false)
    
        // 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.Transfer 完成以太币的转账操作,然后在stateDB中找到并设置智能合约的代码,最后通过 run 函数在 EVM 中执行智能合约的字节码, 如果执行过程中发生错误则回滚先前的操作, 返回执行结果。至此,这笔交易的执行结果就被写到了区块链中。

    总结

    以太坊中一笔交易的执行流程大致为:

    客户端构造交易 -> 通过p2p网络广播交易 -> 矿工节点收到交易 -> 将交易反序列化为 Transaction 结构 -> 将交易放到mempool -> 矿工挖矿 -> 在EVM中执行这笔交易 -> 交易执行结果写入stateDB

    相关文章

      网友评论

          本文标题:以太坊中一笔交易的执行流程

          本文链接:https://www.haomeiwen.com/subject/batpzqtx.html