Btcd區(qū)塊鏈的構(gòu)建(一)

我們在介紹Btcd協(xié)議消息時提到,協(xié)議設(shè)計的目標就是同步transaction或者block,最終在各節(jié)點上形成一致的區(qū)塊鏈。本文開始,我們將逐步分析節(jié)點在收到transaction或者block后如何處理它們,如如何將transaction打包成區(qū)塊并挖礦、如何將block添加到區(qū)塊鏈中等問題。同步transaction后,“礦工”可以將交易打包進新的區(qū)塊,并向網(wǎng)絡(luò)傳播新區(qū)塊。所以,對于獨立“挖礦”的節(jié)點或者通過“礦池”與“礦工”連接的“全節(jié)點”而言,它們有兩方面的區(qū)塊來源,即“挖礦”產(chǎn)生的新區(qū)塊和收到的其它節(jié)點轉(zhuǎn)發(fā)的區(qū)塊,如下圖所示。

同步其它節(jié)點轉(zhuǎn)發(fā)的區(qū)塊,就是通過協(xié)議消息inv-getdata-block實現(xiàn)的,我們在《Btcd區(qū)塊鏈協(xié)議消息解析》中介紹過這一過程;“挖礦”的過程我們將在后文中詳細介紹。無論是“挖”出新區(qū)塊還是收到其他節(jié)點轉(zhuǎn)發(fā)的區(qū)塊,都需要經(jīng)過一系列驗證后,再加入到區(qū)塊鏈上;同時,當區(qū)塊鏈的共識機制需要作微調(diào),即發(fā)生“軟分叉”時,節(jié)點之間也需要一個共識機制來支持新的區(qū)塊或者兼容老的區(qū)塊,這些過程或者機制的實現(xiàn)位于btcd/blockchain包中。我們將重點介紹btcd/blockchain包,其中的文件有:

  • fullblocktests文件夾: 包含自動生成測試用例的實現(xiàn)類,用于測試區(qū)塊的共識參數(shù);
  • indexers文件夾: 實現(xiàn)了transaction到block之間和transaction到關(guān)聯(lián)的bitcoin地址之間的索引關(guān)系;
  • testdata文件夾: 包含用于測試的block數(shù)據(jù);
  • process.go: ProcessBlock()方法所在的文件,它是btcd/blockchain對區(qū)塊進行處理的入口方法,也是我們重點分析對象;
  • accept.go: maybeAcceptBlock()方法所在的文件,它是區(qū)塊被鏈接到區(qū)塊鏈上的入口方法;
  • validate.go: 實現(xiàn)了驗證transaction和block的各種方法,如CheckBlockSanity()、CheckProofOfWork、CheckTransactionSanity等等;
  • chain.go: 定義了BlockChain類型,包括了區(qū)塊加入?yún)^(qū)塊鏈,或者區(qū)塊鏈構(gòu)建的主要實現(xiàn)過程;
  • blockindex.go: 實現(xiàn)了blockIndex,用于維護blockchain上block與其hash之間的映射關(guān)系和父block與子block(s)的關(guān)系;
  • chainio.go: 提供了向db中讀寫區(qū)塊或者相關(guān)元數(shù)據(jù)(如ThresholdCaches、BlockIndex等等)的方法;
  • checkpoints.go: 實現(xiàn)了BlockChain中與Checkpoint相關(guān)的方法,如findPreviousCheckpoint()等等,BlockChain中的checkpoints是一系列指定的區(qū)塊,內(nèi)置在bitcoin節(jié)點中,用于驗證區(qū)塊工作量;
  • compress.go: 實現(xiàn)了一種改進的VLQ(Variable Length Quantity)正整數(shù)編碼方式,同時實現(xiàn)了對交易輸出額進行壓縮編碼和輸出腳本(鎖定腳本)進行壓縮編碼的算法,進而實現(xiàn)了對交易輸出TxOut的壓縮;
  • difficulty.go: 提供了與區(qū)塊難度值相關(guān)的方法,如區(qū)塊頭里的難度值與整數(shù)值之間的轉(zhuǎn)換、通過難度值計算工作量和難度值調(diào)整算法等等;
  • mediatime.go: 定義了MedianTimeSource接口和其實現(xiàn)mediaTime類型,通過計算各時間源的中位值來較正當前時間;
  • merkle.go: 實現(xiàn)了構(gòu)建Merkle樹的算法;
  • notifications.go: 定義了NotificationCallback和Notification,用于blockchain向其消費者回調(diào)NTBlockAccepted、NTBlockConnected及NTBlockDisconnected事件;
  • scriptval.go: 實現(xiàn)了驗證交易中的鎖定腳本與解鎖腳本的機制,支持并發(fā)地驗證多個腳本對;
  • thresholdstate.go: 定義了新的共識規(guī)則在向區(qū)塊鏈網(wǎng)絡(luò)部署時的狀態(tài),按部署或者節(jié)點接受的進度依次為Defined、Started、LockedIn、Active或者Failed等狀態(tài),更重要地,實現(xiàn)了狀態(tài)轉(zhuǎn)換的算法和計算區(qū)塊上某個共識規(guī)則的部署狀態(tài)的方法,我們將在后文中詳細介紹;
  • timersort.go: 實現(xiàn)了支持升序排序的時間戳集合類型timeSorter,timeSorter實際上是[]int64類型,這是Go語言特性的一個體現(xiàn),即通過類型定義可以擴展任意類型,包括基礎(chǔ)類型的方法集;
  • versionbits.go: 實現(xiàn)了BIP9中描述的關(guān)于區(qū)塊版本號的定義,區(qū)塊版本號是一個32位且按小端模式存儲的整型值,以001開頭,其后的每一位代表一個BIP部署;同時也實現(xiàn)了根據(jù)部署狀態(tài)確定下一個區(qū)塊的目標版本號的方法,用于設(shè)定新“挖”出區(qū)塊的版本號,我們將在后文中詳細分析;
  • utxoviewpoint.go: 維護了UTXO的集合,包括有新的transaction時向集合中添加新的UTXO或者移除已經(jīng)花費的UTXO;
  • doc.go: 包blockchain的doc說明;
  • error.go: 定義了與構(gòu)建區(qū)塊鏈相關(guān)的error類型及對應(yīng)的字符串;
  • log.go: 提供了logger相關(guān)的方法;
  • xxx_test.go: 各文件對應(yīng)的測試文件;

ProcessBlock()是btcd/blockchain中開始對block進行各種驗證并接入?yún)^(qū)塊鏈的核心方法,從上圖中也可以看到,收到其它節(jié)點同步過來的區(qū)塊或者礦工“挖”出塊后均交由ProcessBlock處理。接下來,我們將從ProcessBlock()入手逐步分析區(qū)塊處理的各個步驟,為了便于后續(xù)分析,我們看介紹BlockChain的定義:

//btcd/blockchain/chain.go

// BlockChain provides functions for working with the bitcoin block chain.
// It includes functionality such as rejecting duplicate blocks, ensuring blocks
// follow all rules, orphan handling, checkpoint handling, and best chain
// selection with reorganization.
type BlockChain struct {
    // The following fields are set when the instance is created and can't
    // be changed afterwards, so there is no need to protect them with a
    // separate mutex.
    checkpoints         []chaincfg.Checkpoint
    checkpointsByHeight map[int32]*chaincfg.Checkpoint
    db                  database.DB
    chainParams         *chaincfg.Params
    timeSource          MedianTimeSource
    notifications       NotificationCallback
    sigCache            *txscript.SigCache
    indexManager        IndexManager

    // The following fields are calculated based upon the provided chain
    // parameters.  They are also set when the instance is created and
    // can't be changed afterwards, so there is no need to protect them with
    // a separate mutex.
    //
    // minMemoryNodes is the minimum number of consecutive nodes needed
    // in memory in order to perform all necessary validation.  It is used
    // to determine when it's safe to prune nodes from memory without
    // causing constant dynamic reloading.  This is typically the same value
    // as blocksPerRetarget, but it is separated here for tweakability and
    // testability.
    minRetargetTimespan int64 // target timespan / adjustment factor
    maxRetargetTimespan int64 // target timespan * adjustment factor
    blocksPerRetarget   int32 // target timespan / target time per block
    minMemoryNodes      int32

    // chainLock protects concurrent access to the vast majority of the
    // fields in this struct below this point.
    chainLock sync.RWMutex

    // These fields are configuration parameters that can be toggled at
    // runtime.  They are protected by the chain lock.
    noVerify bool

    // These fields are related to the memory block index.  The best node
    // is protected by the chain lock and the index has its own locks.
    bestNode *blockNode
    index    *blockIndex

    // These fields are related to handling of orphan blocks.  They are
    // protected by a combination of the chain lock and the orphan lock.
    orphanLock   sync.RWMutex
    orphans      map[chainhash.Hash]*orphanBlock
    prevOrphans  map[chainhash.Hash][]*orphanBlock
    oldestOrphan *orphanBlock

    // These fields are related to checkpoint handling.  They are protected
    // by the chain lock.
    nextCheckpoint  *chaincfg.Checkpoint
    checkpointBlock *btcutil.Block

    // The state is used as a fairly efficient way to cache information
    // about the current best chain state that is returned to callers when
    // requested.  It operates on the principle of MVCC such that any time a
    // new block becomes the best block, the state pointer is replaced with
    // a new struct and the old state is left untouched.  In this way,
    // multiple callers can be pointing to different best chain states.
    // This is acceptable for most callers because the state is only being
    // queried at a specific point in time.
    //
    // In addition, some of the fields are stored in the database so the
    // chain state can be quickly reconstructed on load.
    stateLock     sync.RWMutex
    stateSnapshot *BestState

    // The following caches are used to efficiently keep track of the
    // current deployment threshold state of each rule change deployment.
    //
    // This information is stored in the database so it can be quickly
    // reconstructed on load.
    //
    // warningCaches caches the current deployment threshold state for blocks
    // in each of the **possible** deployments.  This is used in order to
    // detect when new unrecognized rule changes are being voted on and/or
    // have been activated such as will be the case when older versions of
    // the software are being used
    //
    // deploymentCaches caches the current deployment threshold state for
    // blocks in each of the actively defined deployments.
    warningCaches    []thresholdStateCache
    deploymentCaches []thresholdStateCache

    // The following fields are used to determine if certain warnings have
    // already been shown.
    //
    // unknownRulesWarned refers to warnings due to unknown rules being
    // activated.
    //
    // unknownVersionsWarned refers to warnings due to unknown versions
    // being mined.
    unknownRulesWarned    bool
    unknownVersionsWarned bool
}

其中各個字段的意義如下:

  • checkpoints: 鏈上預(yù)設(shè)的一些checkpoints,節(jié)點啟動時指定,實際由鏈上的某些高度上的區(qū)塊充當;
  • checkpointsByHeight: 記錄了checkpoints和對應(yīng)的區(qū)塊的高度之間的映射關(guān)系,初始化時由checkpoints解析而來;
  • db: 存儲區(qū)塊的database對象;
  • chainParams: 與區(qū)塊鏈相關(guān)的參數(shù)配置,如網(wǎng)絡(luò)號、創(chuàng)世區(qū)塊(Genesis Block)、難度調(diào)整周期、DNSSeeds等等;對這些參數(shù)進行定制即可以生成一個與Bitcoin類似的新的"代幣";
  • timeSource: 用于較正節(jié)點時鐘的“時鐘源”,節(jié)點可以在與Peer進行version消息交換的時候把Peer添加到自己的“時鐘源”中,這樣節(jié)點通過計算與不同Peer節(jié)點的時鐘差的中位值,來估計其與網(wǎng)絡(luò)同步時間的差,從而在利用timestamp進行區(qū)塊較驗之前先較正自己的時鐘,以防節(jié)點時鐘未同步造成較驗失敗;
  • notifications: 向bockchain注冊的回調(diào)事件接收者;
  • sigCache: 用于緩存公鑰與簽名驗證的結(jié)果,對于已經(jīng)通過解鎖腳本和鎖定腳本驗證的交易,對應(yīng)的公鑰和簽名會被緩存,后續(xù)相同的交易驗證時可以直接從緩存中讀到驗證結(jié)果;
  • indexManager: 用于索引鏈上transaction或者block的indexer;
  • minRetargetTimespan: 難度調(diào)整的最小周期,公鏈上其值為3.5天;
  • maxRetargetTimespan: 難度調(diào)整的最大周期,公鏈上其值為56天;
  • blocksPerRetarget: 難度調(diào)整周期內(nèi)的區(qū)塊數(shù),公鏈上難度調(diào)整周期是14天,對應(yīng)的區(qū)塊數(shù)是2016個;
  • minMemoryNodes: 該值暫未用到;
  • chainLock: 保護訪問區(qū)塊鏈的讀寫鎖;
  • noVerify: 是否關(guān)閉腳本驗證的開關(guān);
  • bestNode: 指向主鏈上的“尾”節(jié)點,即高度最高的區(qū)塊;
  • index: 指向一個區(qū)塊索引器,用于索引實例化后內(nèi)存中的各區(qū)塊;
  • orphanLock: 保護“孤兒區(qū)塊”相關(guān)對象的讀寫鎖;
  • orphans: 記錄“孤兒區(qū)塊”與其Hash之間的映射;
  • prevOrphans: 記錄“孤兒區(qū)塊”與其父區(qū)塊Hash之間的映射,當父區(qū)塊寫入?yún)^(qū)塊鏈后,要檢索prevOrphans將對應(yīng)的區(qū)塊從“孤兒區(qū)塊池”從移除并寫入?yún)^(qū)塊鏈;
  • oldestOrphan: 處于“孤兒”狀態(tài)時間最長的區(qū)塊,“孤兒區(qū)塊池”最多維護100個“孤兒區(qū)塊”,當超過限制時,開始移除“最老”的“孤兒區(qū)塊”;
  • stateLock: 保護stateSnapshot的讀寫鎖;
  • stateSnapshot: 主鏈相關(guān)信息的快照;
  • warningCaches: 緩存區(qū)塊對于所有可能的部署的thresholdState,用于當節(jié)點收到大量新的版本的區(qū)塊,且對應(yīng)的共識規(guī)則在新的區(qū)塊里已經(jīng)部署或者即將部署時,發(fā)出警告提示,為了兼容新版本的區(qū)塊,可能需要升級btcd版本;
  • deploymentCaches: 緩存區(qū)塊對于已知的部署提案的thresholdState;
  • unknownRulesWarned: 標識是否已經(jīng)警告過未知共識規(guī)則已經(jīng)部署或者將被部署;
  • unknownVersionsWarned: 標識是否已經(jīng)警告過收到過多未知新版本的區(qū)塊;當有新版本的區(qū)塊時,往往有新的共識規(guī)則正在部署,所以警告未知新版本與未知共識規(guī)則部署是相關(guān)的;

在了解了BlockChain的定義后,我們開始從ProcessBlock()分析處理區(qū)塊的各個環(huán)節(jié):

//btcd/blockchain/process.go

// ProcessBlock is the main workhorse for handling insertion of new blocks into
// the block chain.  It includes functionality such as rejecting duplicate
// blocks, ensuring blocks follow all rules, orphan handling, and insertion into
// the block chain along with best chain selection and reorganization.
//
// When no errors occurred during processing, the first return value indicates
// whether or not the block is on the main chain and the second indicates
// whether or not the block is an orphan.
//
// This function is safe for concurrent access.
func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bool, bool, error) {
    b.chainLock.Lock()
    defer b.chainLock.Unlock()

    fastAdd := flags&BFFastAdd == BFFastAdd
    dryRun := flags&BFDryRun == BFDryRun

    blockHash := block.Hash()
    log.Tracef("Processing block %v", blockHash)

    // The block must not already exist in the main chain or side chains.
    exists, err := b.blockExists(blockHash)                                              (1)
    if err != nil {
        return false, false, err
    }
    if exists {
        str := fmt.Sprintf("already have block %v", blockHash)
        return false, false, ruleError(ErrDuplicateBlock, str)
    }

    // The block must not already exist as an orphan.
    if _, exists := b.orphans[*blockHash]; exists {                                      (2)
        str := fmt.Sprintf("already have block (orphan) %v", blockHash)
        return false, false, ruleError(ErrDuplicateBlock, str)
    }

    // Perform preliminary sanity checks on the block and its transactions.
    err = checkBlockSanity(block, b.chainParams.PowLimit, b.timeSource, flags)           (3)
    if err != nil {
        return false, false, err
    }

    // Find the previous checkpoint and perform some additional checks based
    // on the checkpoint.  This provides a few nice properties such as
    // preventing old side chain blocks before the last checkpoint,
    // rejecting easy to mine, but otherwise bogus, blocks that could be
    // used to eat memory, and ensuring expected (versus claimed) proof of
    // work requirements since the previous checkpoint are met.
    blockHeader := &block.MsgBlock().Header
    checkpointBlock, err := b.findPreviousCheckpoint()
    if err != nil {
        return false, false, err
    }
    if checkpointBlock != nil {
        // Ensure the block timestamp is after the checkpoint timestamp.
        checkpointHeader := &checkpointBlock.MsgBlock().Header
        checkpointTime := checkpointHeader.Timestamp
        if blockHeader.Timestamp.Before(checkpointTime) {                                (4)
            str := fmt.Sprintf("block %v has timestamp %v before "+
                "last checkpoint timestamp %v", blockHash,
                blockHeader.Timestamp, checkpointTime)
            return false, false, ruleError(ErrCheckpointTimeTooOld, str)
        }
        if !fastAdd {
            // Even though the checks prior to now have already ensured the
            // proof of work exceeds the claimed amount, the claimed amount
            // is a field in the block header which could be forged.  This
            // check ensures the proof of work is at least the minimum
            // expected based on elapsed time since the last checkpoint and
            // maximum adjustment allowed by the retarget rules.
            duration := blockHeader.Timestamp.Sub(checkpointTime)
            requiredTarget := CompactToBig(b.calcEasiestDifficulty(
                checkpointHeader.Bits, duration))
            currentTarget := CompactToBig(blockHeader.Bits)
            if currentTarget.Cmp(requiredTarget) > 0 {                                   (5)
                str := fmt.Sprintf("block target difficulty of %064x "+
                    "is too low when compared to the previous "+
                    "checkpoint", currentTarget)
                return false, false, ruleError(ErrDifficultyTooLow, str)
            }
        }
    }

    // Handle orphan blocks.
    prevHash := &blockHeader.PrevBlock
    prevHashExists, err := b.blockExists(prevHash)
    if err != nil {
        return false, false, err
    }
    if !prevHashExists {
        if !dryRun {
            log.Infof("Adding orphan block %v with parent %v",
                blockHash, prevHash)
            b.addOrphanBlock(block)                                                      (6)
        }

        return false, true, nil
    }

    // The block has passed all context independent checks and appears sane
    // enough to potentially accept it into the block chain.
    isMainChain, err := b.maybeAcceptBlock(block, flags)                                 (7)
    if err != nil {
        return false, false, err
    }

    // Don't process any orphans or log when the dry run flag is set.
    if !dryRun {
        // Accept any orphan blocks that depend on this block (they are
        // no longer orphans) and repeat for those accepted blocks until
        // there are no more.
        err := b.processOrphans(blockHash, flags)                                        (8)
        if err != nil {
            return false, false, err
        }

        log.Debugf("Accepted block %v", blockHash)
    }

    return isMainChain, false, nil
}

ProcessBlock()輸入的是指向btcutil.Block類型的block,它對wire.MsgBlock進行了封裝,可以看作是訪問wire.MsgBlock的輔助類型,在btcd/blockchain中看到的block類型均是btcutil.Block類型,所以在解析代碼之前,我們先看一下它的定義:

//btcd/vendor/github.com/btcsuite/btcutil/block.go

// Block defines a bitcoin block that provides easier and more efficient
// manipulation of raw blocks.  It also memoizes hashes for the block and its
// transactions on their first access so subsequent accesses don't have to
// repeat the relatively expensive hashing operations.
type Block struct {
    msgBlock        *wire.MsgBlock  // Underlying MsgBlock
    serializedBlock []byte          // Serialized bytes for the block
    blockHash       *chainhash.Hash // Cached block hash
    blockHeight     int32           // Height in the main block chain
    transactions    []*Tx           // Transactions
    txnsGenerated   bool            // ALL wrapped transactions generated
}

ProcessBlock()輸出的第一個值表示區(qū)塊是否加入了主鏈,第二值表示區(qū)塊是否是“孤兒區(qū)塊”。其主要執(zhí)行步驟是:

  1. 首先檢查區(qū)塊是否已經(jīng)在鏈上,如代碼(1)處所示;
  2. 然后檢查區(qū)塊是否在“孤兒區(qū)塊池”中,如代碼(2)處所示;
  3. 代碼(3)處調(diào)用checkBlockSanity()對區(qū)塊結(jié)構(gòu)進行檢查,包括對區(qū)塊頭,如工作量和Merkle樹根等等,和交易集合的檢查;
  4. 通過區(qū)塊結(jié)構(gòu)檢查后,根據(jù)最近的Checkpoint與區(qū)塊之間的時間差,計算預(yù)期的最小工作量,如果區(qū)塊的工作量低于預(yù)期的最小工作量則被拒絕,如代碼(5)所示; 這是通過Checkpoint機制防止偽造工作量證明的過程,需要注意的是,區(qū)塊頭中表示目標難度的值越大,則表示工作量越小,反之,其值越小,則表示工作量越大;
  5. 通過Checkpoint檢查后,接著檢測區(qū)塊的父區(qū)塊是否在鏈上,如果不在鏈上,則將區(qū)塊加入“孤兒區(qū)塊池”,如代碼(6)處所示;
  6. 如果父區(qū)塊在鏈上,代碼(7)處調(diào)用maybeAcceptBlock()對區(qū)塊先進行上下文檢查,如根據(jù)父區(qū)塊計算期望工作量、期望的timestamp范圍、區(qū)塊高度是否正確等等,然后根據(jù)父區(qū)塊是否在主鏈上決定是擴展主鏈還是側(cè)鏈,并進一步對區(qū)塊中的交易進行驗證;交易驗證通過后,最終將區(qū)塊接入?yún)^(qū)塊鏈,如果擴展的是側(cè)鏈,還要比較側(cè)鏈擴展后的工作量是否超過主鏈,如果超過,則將側(cè)鏈變?yōu)橹麈湥渲性敿毜倪^程將在后文中介紹;
  7. 區(qū)塊加入?yún)^(qū)塊鏈后,最后調(diào)用processOrphans()檢測“孤兒區(qū)塊池”中是否有“孤兒區(qū)塊”的父區(qū)塊是剛剛?cè)腈湹膮^(qū)塊,如果有,則將“孤兒區(qū)塊”也加入?yún)^(qū)塊鏈,并重復(fù)這一檢查;

通過ProcessBlock()我們可以看到區(qū)塊處理的幾個階段:

其中的checkBlockSanity、maybeAcceptBlock及processOrphans等過程中又有很復(fù)雜的步驟,它們到底作了哪些檢查,如何保證區(qū)塊鏈的一致性,我們將在下一篇文章《Btcd區(qū)塊鏈的構(gòu)建(二)》中展開介紹。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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