美文网首页区块链研习社工作日志
比特币探究之交易打包

比特币探究之交易打包

作者: 魏兆华 | 来源:发表于2018-07-20 17:57 被阅读19次

    交易打包是在addPackageTxs函数,在src/miner.cpp里。在解析它之前,先看一下它用到的重要数据结构。

    //对交易池中每个交易进行再打包,用于计算包含祖先交易的费率,便于用来排序
    struct CTxMemPoolModifiedEntry {
        //显式构造函数,参数只需要一个txiter,其实就是CTxMemPoolEntry
        explicit CTxMemPoolModifiedEntry(CTxMemPool::txiter entry)
        {
            iter = entry;
            nSizeWithAncestors = entry->GetSizeWithAncestors();
            nModFeesWithAncestors = entry->GetModFeesWithAncestors();
            nSigOpCostWithAncestors = entry->GetSigOpCostWithAncestors();
        }
    
        int64_t GetModifiedFee() const { return iter->GetModifiedFee(); }
        uint64_t GetSizeWithAncestors() const { return nSizeWithAncestors; }
        CAmount GetModFeesWithAncestors() const { return nModFeesWithAncestors; }
        size_t GetTxSize() const { return iter->GetTxSize(); }
        const CTransaction& GetTx() const { return iter->GetTx(); }
    
        CTxMemPool::txiter iter;
        uint64_t nSizeWithAncestors;
        CAmount nModFeesWithAncestors;
        int64_t nSigOpCostWithAncestors;
    };
    

    接下来是几个辅助结构:

    //第一个比较运算子,只是简单地比较内存地址,用来作索引
    struct CompareCTxMemPoolIter {
        bool operator()(const CTxMemPool::txiter& a, const CTxMemPool::txiter& b) const
        {
            return &(*a) < &(*b);
        }
    };
    
    //用于提取交易(CTxMemPoolEntry)的运算子
    struct modifiedentry_iter {
        typedef CTxMemPool::txiter result_type;
        result_type operator() (const CTxMemPoolModifiedEntry &entry) const
        {
            return entry.iter;
        }
    };
    
    //第二个比较运算子,比较的是包含祖先交易的费率(fee/size),如果相等,就比较交易Hash
    //这里的祖先交易是指在交易池里还未被确认的
    //这个运算子定义在txmempool.h里
    class CompareTxMemPoolEntryByAncestorFee
    {
    public:
        template<typename T>
        bool operator()(const T& a, const T& b) const
        {
            double a_mod_fee, a_size, b_mod_fee, b_size;
    
            GetModFeeAndSize(a, a_mod_fee, a_size);
            GetModFeeAndSize(b, b_mod_fee, b_size);
    
            //变除为乘,小而管用的运算技巧
            double f1 = a_mod_fee * b_size;
            double f2 = a_size * b_mod_fee;
    
            if (f1 == f2) {
                return a.GetTx().GetHash() < b.GetTx().GetHash();
            }
            return f1 > f2;
        }
    
        //获取费率(fee/size),用于排序
        template <typename T>
        void GetModFeeAndSize(const T &a, double &mod_fee, double &size) const
        {
            //两种算法,取其小
            double f1 = (double)a.GetModifiedFee() * a.GetSizeWithAncestors();
            double f2 = (double)a.GetModFeesWithAncestors() * a.GetTxSize();
    
            if (f1 > f2) {
                mod_fee = a.GetModFeesWithAncestors();
                size = a.GetSizeWithAncestors();
            } else {
                mod_fee = a.GetModifiedFee();
                size = a.GetTxSize();
            }
        }
    };
    

    OK,可以引出交易打包使用的多重索引集合:

    typedef boost::multi_index_container<
        CTxMemPoolModifiedEntry, //集合中的项目类型就是CTxMemPoolModifiedEntry
        boost::multi_index::indexed_by<
            boost::multi_index::ordered_unique<
                modifiedentry_iter,  //唯一性索引,基于内部的CTxMemPoolEntry
                CompareCTxMemPoolIter  //地址比较运算子
            >,
            //第二个索引,非唯一性,基于包含祖先交易的费率
            boost::multi_index::ordered_non_unique<
                //使用ancestor_score作为索引tag,它是一个空结构,定义在txmempool.h里
                boost::multi_index::tag<ancestor_score>,
                boost::multi_index::identity<CTxMemPoolModifiedEntry>,
                CompareTxMemPoolEntryByAncestorFee  //包含祖先交易的费率比较运算子
            >
        >
    > indexed_modified_transaction_set;
    //下面是两个迭代子,分别对应两个索引
    typedef indexed_modified_transaction_set::nth_index<0>::type::iterator modtxiter;
    typedef indexed_modified_transaction_set::index<ancestor_score>::type::iterator modtxscoreiter;
    
    最后进入今天的主菜,交易是怎么打包的。先看简要流程图: 交易打包简要流程图

    详细解析见以下源码和注释:

    //交易选择算法:按照交易费率(包含未被确认的祖先交易)排序
    //拟入块的交易需要更新其祖先、子孙相关信息,又不能影响交易池中原始信息,
    //因此需要一个新的mapModifiedTx来存储处理过的交易信息
    //打包过程中,不断从mapModifiedTx和mapTx中抽取费率最高的交易,
    //选择其中费率更高的进行打包,并完成相关处理
    void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpdated)
    {
        //mapModifiedTx存储是的待入块的交易,按照预定索引排序
        //并根据其祖先、子孙交易的入块情况,更新交易关联信息(大小、费率等)
        indexed_modified_transaction_set mapModifiedTx;
        //入块失败的交易存在这里,防止重复操作
        CTxMemPool::setEntries failedTx;
    
        //对已经入块的交易,其子孙交易加入mapModifiedTx。
        //若子孙交易已入块则不作处理,否则,减去已入块交易的Size、FeeRate等
        //inBlock是txiter的集合(setEntries),存储的是所有已入块的交易
        UpdatePackagesForAdded(inBlock, mapModifiedTx);
    
        //mi,MempoolItem,用来迭代整个交易池里的所有交易
        CTxMemPool::indexed_transaction_set::index<ancestor_score>::type::iterator mi = mempool.mapTx.get<ancestor_score>().begin();
        CTxMemPool::txiter iter;
    
        //限定区块将满时添加交易的尝试次数,确保在交易过多时及时结束打包过程
        const int64_t MAX_CONSECUTIVE_FAILURES = 1000;
        int64_t nConsecutiveFailed = 0;
    
        //按照包含祖先费率排序,逐个迭代交易
        while (mi != mempool.mapTx.get<ancestor_score>().end() || !mapModifiedTx.empty())
        {
            //如果已经在mapModifiedTx、inBlock或failedTx中,跳过去不处理
            if (mi != mempool.mapTx.get<ancestor_score>().end() &&
                    SkipMapTxEntry(mempool.mapTx.project<0>(mi), mapModifiedTx, failedTx)) {
                ++mi;
                continue;
            }
    
            //下一个待处理的交易,是从mapTx选呢?还是从mapModifiedTx选?
            bool fUsingModified = false;
    
            modtxscoreiter modit = mapModifiedTx.get<ancestor_score>().begin();
            if (mi == mempool.mapTx.get<ancestor_score>().end()) {
                //mapTx已经空了,就从mapModifiedTx选
                iter = modit->iter;
                fUsingModified = true;
            } else {
                //没空的话,比较一下mapTx和mapModifiedTx的当前项
                iter = mempool.mapTx.project<0>(mi);
                if (modit != mapModifiedTx.get<ancestor_score>().end() &&
                        CompareTxMemPoolEntryByAncestorFee()(*modit, CTxMemPoolModifiedEntry(iter))) {
                    //mapModifiedTx当前项费率更高,选它了
                    iter = modit->iter;
                    fUsingModified = true;
                } else {
                    //mapModifiedTx空了,或者没有mapTx当前项费率高,就用mapTx的
                    ++mi;
                }
            }
    
            //inBlock检查前面已经做过了,这里的iter肯定不在inBlock里
            assert(!inBlock.count(iter));
    
            uint64_t packageSize = iter->GetSizeWithAncestors();
            CAmount packageFees = iter->GetModFeesWithAncestors();
            int64_t packageSigOpsCost = iter->GetSigOpCostWithAncestors();
            if (fUsingModified) {
                packageSize = modit->nSizeWithAncestors;
                packageFees = modit->nModFeesWithAncestors;
                packageSigOpsCost = modit->nSigOpCostWithAncestors;
            }
    
            if (packageFees < blockMinFeeRate.GetFee(packageSize)) {
                //少于最低费用,退出
                return;
            }
    
            if (!TestPackage(packageSize, packageSigOpsCost)) {
                if (fUsingModified) {
                    //TestPackage主要是检查区块大小有没有超标
                    //如果超了,最后一个从mapModifiedTx选的,那么从中删掉它,移到failedTx里
                    mapModifiedTx.get<ancestor_score>().erase(modit);
                    failedTx.insert(iter);
                }
    
                ++nConsecutiveFailed;
    
                if (nConsecutiveFailed > MAX_CONSECUTIVE_FAILURES && nBlockWeight >
                        nBlockMaxWeight - 4000) {
                    //最多重试次数到了,区块也快满了,退出循环
                    break;
                }
                continue;
            }
    
            //找到iter的所有祖先
            CTxMemPool::setEntries ancestors;
            uint64_t nNoLimit = std::numeric_limits<uint64_t>::max();
            std::string dummy;
            mempool.CalculateMemPoolAncestors(*iter, ancestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy, false);
            //只需要未被确认的祖先,已入块的都去掉
            onlyUnconfirmed(ancestors);
            ancestors.insert(iter);
    
            //确认交易终结状态,包括锁定点和见证
            if (!TestPackageTransactions(ancestors)) {
                if (fUsingModified) {
                    mapModifiedTx.get<ancestor_score>().erase(modit);
                    failedTx.insert(iter);
                }
                continue;
            }
    
            //交易可以入块,重试次数清零
            nConsecutiveFailed = 0;
    
            //交易及其所有祖先,先排好序
            std::vector<CTxMemPool::txiter> sortedEntries;
            SortForBlock(ancestors, sortedEntries);
            //逐个入块,并从mapModifiedTx中删除
            for (size_t i=0; i<sortedEntries.size(); ++i) {
                AddToBlock(sortedEntries[i]);
                mapModifiedTx.erase(sortedEntries[i]);
            }
    
            ++nPackagesSelected;
    
            //所有已入块交易的子孙交易,更新相关信息
            nDescendantsUpdated += UpdatePackagesForAdded(ancestors, mapModifiedTx);
        }
    }
    

    OK,打包过程就是这样。下一节研究工作量证明(POW)。


    欢迎转载,请注明出处。

    相关文章

      网友评论

        本文标题:比特币探究之交易打包

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