美文网首页区块链进阶之路区块链区块链研习社
以太坊一个区块可以包含多少交易

以太坊一个区块可以包含多少交易

作者: 六天天天向上 | 来源:发表于2019-11-22 16:49 被阅读0次

    今天在做以太坊开发时,突然想到一个问题,以太坊一个区块包含的交易个数由什么决定?

    如果交易池中有足够的交易,一个区块最多可容纳多少交易?

    带着这个问题,我阅读了一下go-ethereum中关于挖矿的源代码,找到了答案。

    先抛出结论:

    影响一个区块交易个数的因素有两个:

    1. 交易池中的交易个数。这个很好理解,交易池的交易个数直接决定了一个矿工可以打包多少交易到区块中。
    2. 区块允许的GasLimit。GasLimit又由父块GasLimit、GasFloor和GasCeil共同决定(1.8版本以前只受父块GasLimit影响),当交易的gas总计大于GasLimit时,交易将不在打包的区块中。

    其中gasFloor和gasCeil是在geth的中mine的两个配置项。详见文档:https://geth.ethereum.org/docs/interface/command-line-options

    image.png

    结论主要从"go-ethereum/miner/worker.go"源文件中得出。worker相当于一个挖矿工人,负责具体的挖矿工作流程。在worker对象中有一个“commitNewWork”方法,是创建一个新的区块,其中计算了该区块的GasLimit和需要处理的交易池中的交易。相关代码如下:

    func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) {
        // 创建区块头
        header := &types.Header{
            ParentHash: parent.Hash(),
            Number:     num.Add(num, common.Big1),
            // 计算GasLimit
            GasLimit:   core.CalcGasLimit(parent, w.config.GasFloor, w.config.GasCeil),
            Extra:      w.extra,
            Time:       uint64(timestamp),
        }
    
        // 从交易池中读取交易
        pending, err := w.eth.TxPool().Pending()
        if err != nil {
            log.Error("Failed to fetch pending transactions", "err", err)
            return
        }
    
        // Short circuit if there is no available pending transactions
        if len(pending) == 0 {
            w.updateSnapshot()
            return
        }
    
        // Split the pending transactions into locals and remotes
        localTxs, remoteTxs := make(map[common.Address]types.Transactions), pending
        for _, account := range w.eth.TxPool().Locals() {
            if txs := remoteTxs[account]; len(txs) > 0 {
                delete(remoteTxs, account)
                localTxs[account] = txs
            }
        }
    
        if len(localTxs) > 0 {
            txs := types.NewTransactionsByPriceAndNonce(w.current.signer, localTxs)
            if w.commitTransactions(txs, w.coinbase, interrupt) {
                return
            }
        }
    
        if len(remoteTxs) > 0 {
            txs := types.NewTransactionsByPriceAndNonce(w.current.signer, remoteTxs)
            if w.commitTransactions(txs, w.coinbase, interrupt) {
                return
            }
        }
    }
    
    

    “commitNewWork”方法中获取到交易池中的交易后将交易提交给了“commitTransactions”方法对交易进行验证。

    func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coinbase common.Address, interrupt *int32) bool {
        // 将区块头中的GasLimit赋值给gasPool
        if w.current.gasPool == nil {
            w.current.gasPool = new(core.GasPool).AddGas(w.current.header.GasLimit)
        }
    
        // 循环判断所有交易
        for {
            // gasPool中的gas小于21000是跳出循环
            if w.current.gasPool.Gas() < params.TxGas {
                log.Trace("Not enough gas for further transactions", "have", w.current.gasPool, "want", params.TxGas)
                break
            }
    
            // 按照交易gas从大到小的顺序获取下一个交易
            tx := txs.Peek()
    
            // 没有交易后跳出循环
            if tx == nil {
                break
            }
            // 提交交易
            logs, err := w.commitTransaction(tx, coinbase)
        }
    }
    

    将交易提交给“commitTransaction”方法后,回调用

    receipt, _, err := core.ApplyTransaction(w.config, w.chain, &coinbase, w.current.gasPool, w.current.state, w.current.header, tx, &w.current.header.GasUsed, vm.Config{})
    

    在ApplyTransaction方法中会将gasPool中的gas值减去该笔交易的gas,当gasPool的gas值小于21000时,剩下的交易将不在打包的该区块中。

    回头在来看一下区块中的GasLimit是怎么计算的。

    core.CalcGasLimit(parent, w.config.GasFloor, w.config.GasCeil)方法在“go-ethereum/core/block_validator.go”,代码如下:

    // CalcGasLimit computes the gas limit of the next block after parent. It aims
    // to keep the baseline gas above the provided floor, and increase it towards the
    // ceil if the blocks are full. If the ceil is exceeded, it will always decrease
    // the gas allowance.
    func CalcGasLimit(parent *types.Block, gasFloor, gasCeil uint64) uint64 {
        // contrib = (parentGasUsed * 3 / 2) / 1024
        contrib := (parent.GasUsed() + parent.GasUsed()/2) / params.GasLimitBoundDivisor
    
        // decay = parentGasLimit / 1024 -1
        decay := parent.GasLimit()/params.GasLimitBoundDivisor - 1
    
        /*
            strategy: gasLimit of block-to-mine is set based on parent's
            gasUsed value.  if parentGasUsed > parentGasLimit * (2/3) then we
            increase it, otherwise lower it (or leave it unchanged if it's right
            at that usage) the amount increased/decreased depends on how far away
            from parentGasLimit * (2/3) parentGasUsed is.
        */
        limit := parent.GasLimit() - decay + contrib
        if limit < params.MinGasLimit {
            limit = params.MinGasLimit
        }
        // If we're outside our allowed gas range, we try to hone towards them
        if limit < gasFloor {
            limit = parent.GasLimit() + decay
            if limit > gasFloor {
                limit = gasFloor
            }
        } else if limit > gasCeil {
            limit = parent.GasLimit() - decay
            if limit < gasCeil {
                limit = gasCeil
            }
        }
        return limit
    }
    

    计算GasLimit的策略很有意思,受父块的GasLimit、挖矿的gas上限gasCeil和gas下限gasFloor三者共同决定。

    当父块的交易里的gas总和大于父块GasLimit的2/3时,则增加当前块的GasLimit,反之减少当前块的GasLimit,同时,保证GasLimit的范围在gasFloor和gasCeil之间。

    相关文章

      网友评论

        本文标题:以太坊一个区块可以包含多少交易

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