比特幣探究之交易打包

交易打包是在addPackageTxs函數(shù),在src/miner.cpp里坐梯。在解析它之前票髓,先看一下它用到的重要數(shù)據(jù)結(jié)構(gòu)。

//對(duì)交易池中每個(gè)交易進(jìn)行再打包托启,用于計(jì)算包含祖先交易的費(fèi)率,便于用來(lái)排序
struct CTxMemPoolModifiedEntry {
    //顯式構(gòu)造函數(shù)攘宙,參數(shù)只需要一個(gè)txiter屯耸,其實(shí)就是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;
};

接下來(lái)是幾個(gè)輔助結(jié)構(gòu):

//第一個(gè)比較運(yùn)算子,只是簡(jiǎn)單地比較內(nèi)存地址蹭劈,用來(lái)作索引
struct CompareCTxMemPoolIter {
    bool operator()(const CTxMemPool::txiter& a, const CTxMemPool::txiter& b) const
    {
        return &(*a) < &(*b);
    }
};

//用于提取交易(CTxMemPoolEntry)的運(yùn)算子
struct modifiedentry_iter {
    typedef CTxMemPool::txiter result_type;
    result_type operator() (const CTxMemPoolModifiedEntry &entry) const
    {
        return entry.iter;
    }
};

//第二個(gè)比較運(yùn)算子疗绣,比較的是包含祖先交易的費(fèi)率(fee/size),如果相等铺韧,就比較交易Hash
//這里的祖先交易是指在交易池里還未被確認(rèn)的
//這個(gè)運(yùn)算子定義在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);

        //變除為乘多矮,小而管用的運(yùn)算技巧
        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;
    }

    //獲取費(fèi)率(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, //集合中的項(xiàng)目類(lèi)型就是CTxMemPoolModifiedEntry
    boost::multi_index::indexed_by<
        boost::multi_index::ordered_unique<
            modifiedentry_iter,  //唯一性索引,基于內(nèi)部的CTxMemPoolEntry
            CompareCTxMemPoolIter  //地址比較運(yùn)算子
        >,
        //第二個(gè)索引料仗,非唯一性湾盗,基于包含祖先交易的費(fèi)率
        boost::multi_index::ordered_non_unique<
            //使用ancestor_score作為索引tag,它是一個(gè)空結(jié)構(gòu)立轧,定義在txmempool.h里
            boost::multi_index::tag<ancestor_score>,
            boost::multi_index::identity<CTxMemPoolModifiedEntry>,
            CompareTxMemPoolEntryByAncestorFee  //包含祖先交易的費(fèi)率比較運(yùn)算子
        >
    >
> indexed_modified_transaction_set;
//下面是兩個(gè)迭代子格粪,分別對(duì)應(yīng)兩個(gè)索引
typedef indexed_modified_transaction_set::nth_index<0>::type::iterator modtxiter;
typedef indexed_modified_transaction_set::index<ancestor_score>::type::iterator modtxscoreiter;

最后進(jìn)入今天的主菜躏吊,交易是怎么打包的。先看簡(jiǎn)要流程圖:
交易打包簡(jiǎn)要流程圖

詳細(xì)解析見(jiàn)以下源碼和注釋?zhuān)?/p>

//交易選擇算法:按照交易費(fèi)率(包含未被確認(rèn)的祖先交易)排序
//擬入塊的交易需要更新其祖先帐萎、子孫相關(guān)信息比伏,又不能影響交易池中原始信息,
//因此需要一個(gè)新的mapModifiedTx來(lái)存儲(chǔ)處理過(guò)的交易信息
//打包過(guò)程中吓肋,不斷從mapModifiedTx和mapTx中抽取費(fèi)率最高的交易,
//選擇其中費(fèi)率更高的進(jìn)行打包瑰艘,并完成相關(guān)處理
void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpdated)
{
    //mapModifiedTx存儲(chǔ)是的待入塊的交易是鬼,按照預(yù)定索引排序
    //并根據(jù)其祖先、子孫交易的入塊情況紫新,更新交易關(guān)聯(lián)信息(大小均蜜、費(fèi)率等)
    indexed_modified_transaction_set mapModifiedTx;
    //入塊失敗的交易存在這里,防止重復(fù)操作
    CTxMemPool::setEntries failedTx;

    //對(duì)已經(jīng)入塊的交易芒率,其子孫交易加入mapModifiedTx囤耳。
    //若子孫交易已入塊則不作處理,否則偶芍,減去已入塊交易的Size充择、FeeRate等
    //inBlock是txiter的集合(setEntries),存儲(chǔ)的是所有已入塊的交易
    UpdatePackagesForAdded(inBlock, mapModifiedTx);

    //mi匪蟀,MempoolItem椎麦,用來(lái)迭代整個(gè)交易池里的所有交易
    CTxMemPool::indexed_transaction_set::index<ancestor_score>::type::iterator mi = mempool.mapTx.get<ancestor_score>().begin();
    CTxMemPool::txiter iter;

    //限定區(qū)塊將滿(mǎn)時(shí)添加交易的嘗試次數(shù),確保在交易過(guò)多時(shí)及時(shí)結(jié)束打包過(guò)程
    const int64_t MAX_CONSECUTIVE_FAILURES = 1000;
    int64_t nConsecutiveFailed = 0;

    //按照包含祖先費(fèi)率排序材彪,逐個(gè)迭代交易
    while (mi != mempool.mapTx.get<ancestor_score>().end() || !mapModifiedTx.empty())
    {
        //如果已經(jīng)在mapModifiedTx观挎、inBlock或failedTx中,跳過(guò)去不處理
        if (mi != mempool.mapTx.get<ancestor_score>().end() &&
                SkipMapTxEntry(mempool.mapTx.project<0>(mi), mapModifiedTx, failedTx)) {
            ++mi;
            continue;
        }

        //下一個(gè)待處理的交易段化,是從mapTx選呢嘁捷?還是從mapModifiedTx選?
        bool fUsingModified = false;

        modtxscoreiter modit = mapModifiedTx.get<ancestor_score>().begin();
        if (mi == mempool.mapTx.get<ancestor_score>().end()) {
            //mapTx已經(jīng)空了显熏,就從mapModifiedTx選
            iter = modit->iter;
            fUsingModified = true;
        } else {
            //沒(méi)空的話雄嚣,比較一下mapTx和mapModifiedTx的當(dāng)前項(xiàng)
            iter = mempool.mapTx.project<0>(mi);
            if (modit != mapModifiedTx.get<ancestor_score>().end() &&
                    CompareTxMemPoolEntryByAncestorFee()(*modit, CTxMemPoolModifiedEntry(iter))) {
                //mapModifiedTx當(dāng)前項(xiàng)費(fèi)率更高,選它了
                iter = modit->iter;
                fUsingModified = true;
            } else {
                //mapModifiedTx空了喘蟆,或者沒(méi)有mapTx當(dāng)前項(xiàng)費(fèi)率高现诀,就用mapTx的
                ++mi;
            }
        }

        //inBlock檢查前面已經(jīng)做過(guò)了,這里的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)) {
            //少于最低費(fèi)用履肃,退出
            return;
        }

        if (!TestPackage(packageSize, packageSigOpsCost)) {
            if (fUsingModified) {
                //TestPackage主要是檢查區(qū)塊大小有沒(méi)有超標(biāo)
                //如果超了仔沿,最后一個(gè)從mapModifiedTx選的,那么從中刪掉它尺棋,移到failedTx里
                mapModifiedTx.get<ancestor_score>().erase(modit);
                failedTx.insert(iter);
            }

            ++nConsecutiveFailed;

            if (nConsecutiveFailed > MAX_CONSECUTIVE_FAILURES && nBlockWeight >
                    nBlockMaxWeight - 4000) {
                //最多重試次數(shù)到了封锉,區(qū)塊也快滿(mǎn)了绵跷,退出循環(huán)
                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);
        //只需要未被確認(rèn)的祖先,已入塊的都去掉
        onlyUnconfirmed(ancestors);
        ancestors.insert(iter);

        //確認(rèn)交易終結(jié)狀態(tài)成福,包括鎖定點(diǎn)和見(jiàn)證
        if (!TestPackageTransactions(ancestors)) {
            if (fUsingModified) {
                mapModifiedTx.get<ancestor_score>().erase(modit);
                failedTx.insert(iter);
            }
            continue;
        }

        //交易可以入塊碾局,重試次數(shù)清零
        nConsecutiveFailed = 0;

        //交易及其所有祖先,先排好序
        std::vector<CTxMemPool::txiter> sortedEntries;
        SortForBlock(ancestors, sortedEntries);
        //逐個(gè)入塊奴艾,并從mapModifiedTx中刪除
        for (size_t i=0; i<sortedEntries.size(); ++i) {
            AddToBlock(sortedEntries[i]);
            mapModifiedTx.erase(sortedEntries[i]);
        }

        ++nPackagesSelected;

        //所有已入塊交易的子孫交易净当,更新相關(guān)信息
        nDescendantsUpdated += UpdatePackagesForAdded(ancestors, mapModifiedTx);
    }
}

OK,打包過(guò)程就是這樣蕴潦。下一節(jié)研究工作量證明(POW)像啼。


歡迎轉(zhuǎn)載,請(qǐng)注明出處潭苞。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末忽冻,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子此疹,更是在濱河造成了極大的恐慌僧诚,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蝗碎,死亡現(xiàn)場(chǎng)離奇詭異湖笨,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)蹦骑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)赶么,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人脊串,你說(shuō)我怎么就攤上這事辫呻。” “怎么了琼锋?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵放闺,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我缕坎,道長(zhǎng)怖侦,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任谜叹,我火速辦了婚禮匾寝,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘荷腊。我一直安慰自己艳悔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布女仰。 她就那樣靜靜地躺著猜年,像睡著了一般抡锈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上乔外,一...
    開(kāi)封第一講書(shū)人閱讀 51,155評(píng)論 1 299
  • 那天床三,我揣著相機(jī)與錄音,去河邊找鬼杨幼。 笑死撇簿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的差购。 我是一名探鬼主播四瘫,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼歹撒!你這毒婦竟也來(lái)了莲组?” 一聲冷哼從身側(cè)響起诊胞,我...
    開(kāi)封第一講書(shū)人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤暖夭,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后撵孤,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體迈着,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年邪码,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了裕菠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡闭专,死狀恐怖奴潘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情影钉,我是刑警寧澤画髓,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站平委,受9級(jí)特大地震影響奈虾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜廉赔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一肉微、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蜡塌,春花似錦碉纳、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)笆环。三九已至,卻和暖如春厚者,著一層夾襖步出監(jiān)牢的瞬間躁劣,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工库菲, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留账忘,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓熙宇,卻偏偏與公主長(zhǎng)得像鳖擒,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子烫止,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容