金马带你定投区块链区块链研习社区块链大学

Btcd区块链的构建(一)

2018-05-15  本文已影响198人  oceanken

我们在介绍Btcd协议消息时提到,协议设计的目标就是同步transaction或者block,最终在各节点上形成一致的区块链。本文开始,我们将逐步分析节点在收到transaction或者block后如何处理它们,如如何将transaction打包成区块并挖矿、如何将block添加到区块链中等问题。同步transaction后,“矿工”可以将交易打包进新的区块,并向网络传播新区块。所以,对于独立“挖矿”的节点或者通过“矿池”与“矿工”连接的“全节点”而言,它们有两方面的区块来源,即“挖矿”产生的新区块和收到的其它节点转发的区块,如下图所示。

同步其它节点转发的区块,就是通过协议消息inv-getdata-block实现的,我们在《Btcd区块链协议消息解析》中介绍过这一过程;“挖矿”的过程我们将在后文中详细介绍。无论是“挖”出新区块还是收到其他节点转发的区块,都需要经过一系列验证后,再加入到区块链上;同时,当区块链的共识机制需要作微调,即发生“软分叉”时,节点之间也需要一个共识机制来支持新的区块或者兼容老的区块,这些过程或者机制的实现位于btcd/blockchain包中。我们将重点介绍btcd/blockchain包,其中的文件有:

ProcessBlock()是btcd/blockchain中开始对block进行各种验证并接入区块链的核心方法,从上图中也可以看到,收到其它节点同步过来的区块或者矿工“挖”出块后均交由ProcessBlock处理。接下来,我们将从ProcessBlock()入手逐步分析区块处理的各个步骤,为了便于后续分析,我们看介绍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
}

其中各个字段的意义如下:

在了解了BlockChain的定义后,我们开始从ProcessBlock()分析处理区块的各个环节:

//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()输出的第一个值表示区块是否加入了主链,第二值表示区块是否是“孤儿区块”。其主要执行步骤是:

  1. 首先检查区块是否已经在链上,如代码(1)处所示;
  2. 然后检查区块是否在“孤儿区块池”中,如代码(2)处所示;
  3. 代码(3)处调用checkBlockSanity()对区块结构进行检查,包括对区块头,如工作量和Merkle树根等等,和交易集合的检查;
  4. 通过区块结构检查后,根据最近的Checkpoint与区块之间的时间差,计算预期的最小工作量,如果区块的工作量低于预期的最小工作量则被拒绝,如代码(5)所示; 这是通过Checkpoint机制防止伪造工作量证明的过程,需要注意的是,区块头中表示目标难度的值越大,则表示工作量越小,反之,其值越小,则表示工作量越大;
  5. 通过Checkpoint检查后,接着检测区块的父区块是否在链上,如果不在链上,则将区块加入“孤儿区块池”,如代码(6)处所示;
  6. 如果父区块在链上,代码(7)处调用maybeAcceptBlock()对区块先进行上下文检查,如根据父区块计算期望工作量、期望的timestamp范围、区块高度是否正确等等,然后根据父区块是否在主链上决定是扩展主链还是侧链,并进一步对区块中的交易进行验证;交易验证通过后,最终将区块接入区块链,如果扩展的是侧链,还要比较侧链扩展后的工作量是否超过主链,如果超过,则将侧链变为主链,其中详细的过程将在后文中介绍;
  7. 区块加入区块链后,最后调用processOrphans()检测“孤儿区块池”中是否有“孤儿区块”的父区块是刚刚入链的区块,如果有,则将“孤儿区块”也加入区块链,并重复这一检查;

通过ProcessBlock()我们可以看到区块处理的几个阶段:

其中的checkBlockSanity、maybeAcceptBlock及processOrphans等过程中又有很复杂的步骤,它们到底作了哪些检查,如何保证区块链的一致性,我们将在下一篇文章《Btcd区块链的构建(二)》中展开介绍。

==大家可以关注我的微信公众号,后续文章将在公众号中同步更新:==
上一篇 下一篇

猜你喜欢

热点阅读