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

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

作者: 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