以太坊中一筆交易的執(zhí)行流程

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 每次從這個賬號發(fā)出的交易都會加1,防止重放攻擊
  • Balance 賬號的余額
  • Root 如果這是一個普通賬戶,這個值是空值的hash。如果是一個合約賬號,那么這個哈希值就是這個合約代碼里面用到的存儲組成的MPT樹的跟哈希
  • CodeHash 如果是智能合約賬號,那么通過這個哈希就可以在數(shù)據(jù)庫中直接找到智能合約的字節(jié)碼

源碼位置: 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
}

以太坊世界狀態(tài)的改變是通過操作 stateObject 進行的。這個結(jié)構(gòu)是世界狀態(tài)MPT樹的操作對象。對賬號余額的修改,智能合約狀態(tài)的改變,回滾等操作都通過這個結(jié)構(gòu)進行。

以太坊白皮書說“以太坊是一個基于交易的狀態(tài)機”,狀態(tài)的改變都是通過區(qū)塊中的每一筆交易驅(qū)動。通過 stateObject 我們就可以快速的找到一個賬號的信息。

一筆交易的內(nèi)部表示

源碼位置: 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 賬號隨機數(shù)
  • Price 每個gas需要消耗多少以太坊幣
  • GasLimit 這個操作最多允許消耗的gas
  • Recipient 轉(zhuǎn)賬的地址或者智能合約地址
  • Amount 轉(zhuǎn)賬數(shù)量
  • Payload 如果是普通賬戶,這個payload就相當(dāng)于一個備注功能,如果是合約賬戶那么這個payload就是在調(diào)用合約里面的方法,或者是在創(chuàng)建一個智能合約
  • 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 關(guān)于交易池的一些配置信息,比如: 交易池允許的最大容量、交易池允許的最低gasPrice等信息
  • chainConfig 關(guān)于區(qū)塊的一些全局配置信息
  • gasPrice gas費用
  • chainHeadCh 區(qū)塊鏈的head發(fā)生了改變,需要通過這個chan通知, head發(fā)生改變是因為發(fā)生了 chain reorganazition
  • queue 這個隊列里面保存了賬號不可立即執(zhí)行的交易,也就是nonce不連續(xù)的交易

對交易的驗證:

// 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
  • 轉(zhuǎn)賬數(shù)量不能為負
  • 交易的gas數(shù)量不能超過區(qū)塊最大允許的gas數(shù)量(BGL, block gas limit)
  • 驗證交易簽名的正確性
  • 驗證是否滿足交易池的配置信息
  • 驗證nonce的正確性
  • 驗證賬戶余額是否足夠
  • 驗證固有g(shù)as消耗滿足需求

交易在虛擬機中執(zhí)行

源碼位置: 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結(jié)構(gòu)在虛擬機中執(zhí)行之前都會轉(zhuǎn)換為 Message 結(jié)構(gòu):

// 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 函數(shù)第一個參數(shù) block 表示要在EVM中執(zhí)行的區(qū)塊,第二個參數(shù) stateDB 是要修改的世界狀態(tài)數(shù)據(jù)庫, 第三個參數(shù) cfg 是關(guān)于EVM的配置信息。Process 函數(shù)對區(qū)塊中的每一筆交易調(diào)用 ApplyTransaction 函數(shù)。在這個函數(shù)中為每一筆交易創(chuàng)建新的虛擬機執(zhí)行環(huán)境。接下來的關(guān)鍵調(diào)用鏈:

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
}

在這里會區(qū)別對待普通交易和和創(chuàng)建智能合約的交易。evm.Create 根據(jù)參數(shù)創(chuàng)建智能合約, evm.Call 根據(jù)交易信息在虛擬機中執(zhí)行這筆交易。重點分析下 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 完成以太幣的轉(zhuǎn)賬操作,然后在stateDB中找到并設(shè)置智能合約的代碼,最后通過 run 函數(shù)在 EVM 中執(zhí)行智能合約的字節(jié)碼, 如果執(zhí)行過程中發(fā)生錯誤則回滾先前的操作, 返回執(zhí)行結(jié)果。至此,這筆交易的執(zhí)行結(jié)果就被寫到了區(qū)塊鏈中。

總結(jié)

以太坊中一筆交易的執(zhí)行流程大致為:

客戶端構(gòu)造交易 -> 通過p2p網(wǎng)絡(luò)廣播交易 -> 礦工節(jié)點收到交易 -> 將交易反序列化為 Transaction 結(jié)構(gòu) -> 將交易放到mempool -> 礦工挖礦 -> 在EVM中執(zhí)行這筆交易 -> 交易執(zhí)行結(jié)果寫入stateDB

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容