上一篇文章中,我們分析了checkBlockSanity()的完整過程,了解了對(duì)區(qū)塊結(jié)構(gòu)驗(yàn)證的過程,如對(duì)區(qū)塊頭中目標(biāo)難度值、工作量證明、時(shí)間戳和Merkle樹及區(qū)塊中的交易集合的驗(yàn)證,這些驗(yàn)證通過之后,節(jié)點(diǎn)就會(huì)調(diào)用maybeAcceptBlock()對(duì)區(qū)塊上下文進(jìn)一步驗(yàn)證,并最終將區(qū)塊寫入?yún)^(qū)塊鏈。maybeAcceptBlock()將基于鏈上預(yù)期的難度值進(jìn)一步檢查區(qū)塊頭中的難度值,還要檢查交易中的輸入是否可花費(fèi)(spendable)、是否有雙重支付、是否有重復(fù)交易等等,同時(shí)決定是延長(zhǎng)主鏈還是側(cè)鏈,延長(zhǎng)側(cè)鏈后是否將需要Reorganize將側(cè)鏈變成主鏈;在區(qū)塊加入?yún)^(qū)塊鏈后,還要將區(qū)塊交易中花費(fèi)的UTXO(s)變成spentTxOut,新生成的UTXO(s)添加到UTXO集合中。同時(shí),區(qū)塊加入主鏈后,節(jié)點(diǎn)還要更新交易池mempool以及通知“礦工”停止當(dāng)前“挖礦”過程,進(jìn)入下一個(gè)區(qū)塊的求解,這一過程我們將在后文中詳細(xì)介紹。maybeAcceptBlock()涉及的步驟較checkBlockSanity()更為復(fù)雜,本文將逐步分析其中的過程。
我們先來看maybeAcceptBlock()的實(shí)現(xiàn):
//btcd/blockchain/accept.go
// maybeAcceptBlock potentially accepts a block into the block chain and, if
// accepted, returns whether or not it is on the main chain. It performs
// several validation checks which depend on its position within the block chain
// before adding it. The block is expected to have already gone through
// ProcessBlock before calling this function with it.
//
// The flags modify the behavior of this function as follows:
// - BFDryRun: The memory chain index will not be pruned and no accept
// notification will be sent since the block is not being accepted.
//
// The flags are also passed to checkBlockContext and connectBestChain. See
// their documentation for how the flags modify their behavior.
//
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags) (bool, error) {
dryRun := flags&BFDryRun == BFDryRun
// Get a block node for the block previous to this one. Will be nil
// if this is the genesis block.
prevNode, err := b.index.PrevNodeFromBlock(block) (1)
if err != nil {
log.Errorf("PrevNodeFromBlock: %v", err)
return false, err
}
// The height of this block is one more than the referenced previous
// block.
blockHeight := int32(0)
if prevNode != nil {
blockHeight = prevNode.height + 1
}
block.SetHeight(blockHeight) (2)
// The block must pass all of the validation rules which depend on the
// position of the block within the block chain.
err = b.checkBlockContext(block, prevNode, flags) (3)
if err != nil {
return false, err
}
// Insert the block into the database if it's not already there. Even
// though it is possible the block will ultimately fail to connect, it
// has already passed all proof-of-work and validity tests which means
// it would be prohibitively expensive for an attacker to fill up the
// disk with a bunch of blocks that fail to connect. This is necessary
// since it allows block download to be decoupled from the much more
// expensive connection logic. It also has some other nice properties
// such as making blocks that never become part of the main chain or
// blocks that fail to connect available for further analysis.
err = b.db.Update(func(dbTx database.Tx) error {
return dbMaybeStoreBlock(dbTx, block) (4)
})
if err != nil {
return false, err
}
// Create a new block node for the block and add it to the in-memory
// block chain (could be either a side chain or the main chain).
blockHeader := &block.MsgBlock().Header
newNode := newBlockNode(blockHeader, block.Hash(), blockHeight) (5)
if prevNode != nil {
newNode.parent = prevNode
newNode.height = blockHeight
newNode.workSum.Add(prevNode.workSum, newNode.workSum)
}
// Connect the passed block to the chain while respecting proper chain
// selection according to the chain with the most proof of work. This
// also handles validation of the transaction scripts.
isMainChain, err := b.connectBestChain(newNode, block, flags) (6)
if err != nil {
return false, err
}
// Notify the caller that the new block was accepted into the block
// chain. The caller would typically want to react by relaying the
// inventory to other peers.
if !dryRun {
b.chainLock.Unlock()
b.sendNotification(NTBlockAccepted, block) (7)
b.chainLock.Lock()
}
return isMainChain, nil
}
其主要過程是:
- 調(diào)用blockIndex的PrevNodeFromBlock()方法查找父區(qū)塊。我們前面介紹過, index用于索引實(shí)例化后內(nèi)存中的各區(qū)塊,PrevNodeFromBlock()先從內(nèi)存中查找,如果未找到,再從數(shù)據(jù)庫中查找并構(gòu)造區(qū)塊的實(shí)例化類型blockNode并返回;
- 找到父區(qū)塊后,將當(dāng)前區(qū)塊的高度值設(shè)為父區(qū)塊高度加1。請(qǐng)注意,區(qū)塊結(jié)構(gòu)本身并沒有高度字段,輔助類型btcutil.Block中記錄了區(qū)塊的高度,在隨后的驗(yàn)證中需要用到高度值。BIP34建議,version為2及以上的區(qū)塊中的coinbase交易的解鎖腳本(scriptSig)開頭將包含區(qū)塊高度值;
- 調(diào)用checkBlockContext()檢查區(qū)塊上下文,我們隨后進(jìn)一步分析它;
- 對(duì)區(qū)塊驗(yàn)證通過后,將其(wire.MsgBlock)寫入數(shù)據(jù)庫(區(qū)塊文件),如代碼(4)處所示。請(qǐng)注意,這里區(qū)塊還沒有寫入?yún)^(qū)塊鏈;
- 為當(dāng)前區(qū)塊創(chuàng)建實(shí)例化對(duì)象blockNode,并計(jì)算工作量之和,如代碼(5)處所示;
- 在區(qū)塊的上下文檢查通過之后,調(diào)用connectBestChain()將區(qū)塊寫入?yún)^(qū)塊鏈,我們隨后進(jìn)一步分析它;
- 當(dāng)區(qū)塊成功寫入主鏈或者側(cè)鏈后,向blockManager通知NTBlockAccepted事件,blockManager會(huì)向所有Peer節(jié)點(diǎn)發(fā)送inv或header消息,將新區(qū)塊通告給Peer(s);
在maybeAcceptBlock()中對(duì)區(qū)塊的處理涉及到了blockNode類型,它的定義如下:
//btcd/blockchain/blockindex.go
// blockNode represents a block within the block chain and is primarily used to
// aid in selecting the best chain to be the main chain. The main chain is
// stored into the block database.
type blockNode struct {
// parent is the parent block for this node.
parent *blockNode
// children contains the child nodes for this node. Typically there
// will only be one, but sometimes there can be more than one and that
// is when the best chain selection algorithm is used.
children []*blockNode
// hash is the double sha 256 of the block.
hash chainhash.Hash
// parentHash is the double sha 256 of the parent block. This is kept
// here over simply relying on parent.hash directly since block nodes
// are sparse and the parent node might not be in memory when its hash
// is needed.
parentHash chainhash.Hash
// height is the position in the block chain.
height int32
// workSum is the total amount of work in the chain up to and including
// this node.
workSum *big.Int
// inMainChain denotes whether the block node is currently on the
// the main chain or not. This is used to help find the common
// ancestor when switching chains.
inMainChain bool
// Some fields from block headers to aid in best chain selection and
// reconstructing headers from memory. These must be treated as
// immutable and are intentionally ordered to avoid padding on 64-bit
// platforms.
version int32
bits uint32
nonce uint32
timestamp int64
merkleRoot chainhash.Hash
}
blockNode可以看作是區(qū)塊在內(nèi)存中的實(shí)例化類型,它的主要字段是:
- parent: 指向父區(qū)塊;
- children: 指向子區(qū)塊;
- hash: 區(qū)塊的Hash,指區(qū)塊頭的雙Hash。請(qǐng)注意,區(qū)塊頭中只包含父區(qū)塊的頭Hash,而不包含自身的頭Hash;
- parentHash: 父區(qū)塊的Hash;
- height: 區(qū)塊高度;
- workSum: 從該區(qū)塊到創(chuàng)世區(qū)塊的工作量之和;
- inMainChain: 指明區(qū)塊是否位于主鏈之上;
- version、bits、nonce、timestamp、merkleRoot: 對(duì)應(yīng)于區(qū)塊頭中各字段;
找到父區(qū)塊對(duì)應(yīng)的blockNode對(duì)象后,checkBlockContext()根據(jù)當(dāng)前區(qū)塊和父區(qū)塊來檢查進(jìn)行上下文檢查:
//btcd/blockchain/validate.go
// checkBlockContext peforms several validation checks on the block which depend
// on its position within the block chain.
//
// The flags modify the behavior of this function as follows:
// - BFFastAdd: The transaction are not checked to see if they are finalized
// and the somewhat expensive BIP0034 validation is not performed.
//
// The flags are also passed to checkBlockHeaderContext. See its documentation
// for how the flags modify its behavior.
//
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) checkBlockContext(block *btcutil.Block, prevNode *blockNode, flags BehaviorFlags) error {
// The genesis block is valid by definition.
if prevNode == nil {
return nil
}
// Perform all block header related validation checks.
header := &block.MsgBlock().Header
err := b.checkBlockHeaderContext(header, prevNode, flags) (1)
if err != nil {
return err
}
fastAdd := flags&BFFastAdd == BFFastAdd
if !fastAdd {
// Obtain the latest state of the deployed CSV soft-fork in
// order to properly guard the new validation behavior based on
// the current BIP 9 version bits state.
csvState, err := b.deploymentState(prevNode, chaincfg.DeploymentCSV) (2)
if err != nil {
return err
}
// Once the CSV soft-fork is fully active, we'll switch to
// using the current median time past of the past block's
// timestamps for all lock-time based checks.
blockTime := header.Timestamp
if csvState == ThresholdActive {
medianTime, err := b.index.CalcPastMedianTime(prevNode)
if err != nil {
return err
}
blockTime = medianTime (3)
}
// The height of this block is one more than the referenced
// previous block.
blockHeight := prevNode.height + 1
// Ensure all transactions in the block are finalized.
for _, tx := range block.Transactions() {
if !IsFinalizedTransaction(tx, blockHeight, (4)
blockTime) {
str := fmt.Sprintf("block contains unfinalized "+
"transaction %v", tx.Hash())
return ruleError(ErrUnfinalizedTx, str)
}
}
// Ensure coinbase starts with serialized block heights for
// blocks whose version is the serializedHeightVersion or newer
// once a majority of the network has upgraded. This is part of
// BIP0034.
if ShouldHaveSerializedBlockHeight(header) && (5)
blockHeight >= b.chainParams.BIP0034Height {
coinbaseTx := block.Transactions()[0]
err := checkSerializedHeight(coinbaseTx, blockHeight) (6)
if err != nil {
return err
}
}
}
return nil
}
其主要步驟是:
- 調(diào)用checkBlockHeaderContext()對(duì)區(qū)塊頭進(jìn)行上下文檢查,與checkBlockHeaderSanity()不同的是,checkBlockHeaderContext()要檢查區(qū)塊頭中的難度值、時(shí)間戳等是否滿足鏈上的要求,隨后我們會(huì)進(jìn)一步分析;
- 調(diào)用deploymentState()計(jì)算父區(qū)塊的子區(qū)塊的CSV(包含BIP68、BIP112和BIP113)部署的狀態(tài),也即鏈上預(yù)期的CSV部署狀態(tài),如果是Active的,則根據(jù)BIP[113]的建議,在檢查交易的LockTime時(shí)用MTP(Median Time Past,指前11個(gè)區(qū)塊的timestamp的中位值)而不是區(qū)塊頭中的timestamp來比較,如代碼(3)處所示,這樣做是為了防止“礦工”故意修改區(qū)塊頭中的timestamp,將locktime小于正常區(qū)塊生成時(shí)間的交易打包進(jìn)來,以賺取更多的“小費(fèi)”。鏈上某個(gè)部署的thresholdState狀態(tài)及狀態(tài)之間的轉(zhuǎn)移,我們將在后文中詳細(xì)介紹;
- 隨后,代碼(4)處調(diào)用IsFinalizedTransaction()檢查區(qū)塊中每一個(gè)交易是否均Finalized,它是通過比較區(qū)塊的MTP和交易中的LockTime來確定的: 當(dāng)LockTime為區(qū)塊高度時(shí)(LockTime值小于5x10^8),LockTime值小于區(qū)塊高度時(shí)才被認(rèn)為是Finalized;當(dāng)LockTime為區(qū)塊生成時(shí)間時(shí),LockTime值要小于區(qū)塊的MTP才被認(rèn)為是Finalized。也就是說,只有交易只能被打包進(jìn)高度或者M(jìn)TP大于其LockTime值的區(qū)塊中。特別地,LockTime為零,或者交易所有輸入的Sequence均為OxFFFFFFFF時(shí),交易也被認(rèn)為是Finalized,可以被打包進(jìn)任何區(qū)塊中。請(qǐng)注意,IsFinalizedTransaction()是直接用交易的LockTime進(jìn)行比較的,并沒有采用[BIP68]中的相對(duì)LockTime值,后面我們將會(huì)看到,在區(qū)塊最終寫入主鏈且CSV部署狀態(tài)為Active時(shí),還會(huì)根據(jù)BIP[68]中的建議計(jì)算交易的相對(duì)LockTime值,它是根據(jù)交易的每個(gè)輸入的Sequence值來計(jì)算的,且選擇最大值作為交易的LockTime,我們將在區(qū)塊寫入主鏈時(shí)詳細(xì)介紹;
- 代碼(5)處檢查區(qū)[BIP34]是否應(yīng)用到區(qū)塊中,如果區(qū)塊頭中的版本大于2(高度為227835是最后一個(gè)版本為1的區(qū)塊)且區(qū)塊高度大于227931,則按[BIP34]中的描述,檢查coinbase中是否包含了正確的區(qū)塊高度;
- 代碼6處調(diào)用checkSerializedHeight檢查coinbase中的解鎖腳本的起始處是否包含了正確的區(qū)塊高度值;
接下來,我們來看checkBlockHeaderContext()的實(shí)現(xiàn):
//btcd/blockchain/validate.go
func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, prevNode *blockNode, flags BehaviorFlags) error {
// The genesis block is valid by definition.
if prevNode == nil {
return nil
}
fastAdd := flags&BFFastAdd == BFFastAdd
if !fastAdd {
// Ensure the difficulty specified in the block header matches
// the calculated difficulty based on the previous block and
// difficulty retarget rules.
expectedDifficulty, err := b.calcNextRequiredDifficulty(prevNode,
header.Timestamp) (1)
if err != nil {
return err
}
blockDifficulty := header.Bits
if blockDifficulty != expectedDifficulty { (2)
str := "block difficulty of %d is not the expected value of %d"
str = fmt.Sprintf(str, blockDifficulty, expectedDifficulty)
return ruleError(ErrUnexpectedDifficulty, str)
}
// Ensure the timestamp for the block header is after the
// median time of the last several blocks (medianTimeBlocks).
medianTime, err := b.index.CalcPastMedianTime(prevNode) (3)
if err != nil {
log.Errorf("CalcPastMedianTime: %v", err)
return err
}
if !header.Timestamp.After(medianTime) { (4)
str := "block timestamp of %v is not after expected %v"
str = fmt.Sprintf(str, header.Timestamp, medianTime)
return ruleError(ErrTimeTooOld, str)
}
}
// The height of this block is one more than the referenced previous
// block.
blockHeight := prevNode.height + 1
// Ensure chain matches up to predetermined checkpoints.
blockHash := header.BlockHash()
if !b.verifyCheckpoint(blockHeight, &blockHash) { (5)
str := fmt.Sprintf("block at height %d does not match "+
"checkpoint hash", blockHeight)
return ruleError(ErrBadCheckpoint, str)
}
// Find the previous checkpoint and prevent blocks which fork the main
// chain before it. This prevents storage of new, otherwise valid,
// blocks which build off of old blocks that are likely at a much easier
// difficulty and therefore could be used to waste cache and disk space.
checkpointBlock, err := b.findPreviousCheckpoint()
if err != nil {
return err
}
if checkpointBlock != nil && blockHeight < checkpointBlock.Height() { (6)
str := fmt.Sprintf("block at height %d forks the main chain "+
"before the previous checkpoint at height %d",
blockHeight, checkpointBlock.Height())
return ruleError(ErrForkTooOld, str)
}
// Reject outdated block versions once a majority of the network
// has upgraded. These were originally voted on by BIP0034,
// BIP0065, and BIP0066.
params := b.chainParams
if header.Version < 2 && blockHeight >= params.BIP0034Height || (7)
header.Version < 3 && blockHeight >= params.BIP0066Height ||
header.Version < 4 && blockHeight >= params.BIP0065Height {
str := "new blocks with version %d are no longer valid"
str = fmt.Sprintf(str, header.Version)
return ruleError(ErrBlockVersionTooOld, str)
}
return nil
}
其中主要步驟為:
- 調(diào)用calcNextRequiredDifficulty()根據(jù)難度調(diào)整算法計(jì)算鏈上下一個(gè)區(qū)塊預(yù)期的目標(biāo)難度值,并與當(dāng)前區(qū)塊頭中的難度值進(jìn)行比較,如代碼(1)、(2)處所示,如果區(qū)塊頭中的難度值不符合預(yù)期值,則驗(yàn)證失敗,這可以防止“礦工”故意選擇難度“小”的值;
- 代碼(3)、(4)處調(diào)用CalcPastMedianTime()計(jì)算鏈上最后一個(gè)區(qū)塊的MTP,并與當(dāng)前區(qū)塊頭中的時(shí)間戳比較,如果區(qū)塊頭中的時(shí)間值小于MTP則驗(yàn)證失敗,這保證區(qū)塊的MTP是單調(diào)增長(zhǎng)的;
- 隨后,驗(yàn)證待加入?yún)^(qū)塊是否對(duì)應(yīng)一個(gè)預(yù)置的Checkpoint點(diǎn),如果是,則比較區(qū)塊Hash是否與預(yù)置的Hash值一致,如果不一致則驗(yàn)證失敗,這保證了Checkpoint點(diǎn)的正確性;
- 調(diào)用findPreviousCheckpoint()查找區(qū)塊鏈上最近的Checkpoint點(diǎn),請(qǐng)注意,這里不是查找待加入?yún)^(qū)塊父區(qū)塊之前的Checkpoint點(diǎn),而是節(jié)點(diǎn)上區(qū)塊鏈上高度最高的Checkpoint點(diǎn),如果待加入?yún)^(qū)塊的高度小于最近Checkpoint點(diǎn)的高度,即區(qū)塊試圖在Checkpoint點(diǎn)之間進(jìn)行分叉,則驗(yàn)證失敗,因?yàn)镃heckpoint點(diǎn)之前分叉的側(cè)鏈很可能因工作量之和小于主鏈的工作量之和而沒有機(jī)會(huì)成為主鏈;
- 最后,代碼(7)處檢查BIP34、BIP66和BIP65所對(duì)應(yīng)的區(qū)塊高度和版本號(hào)是否達(dá)到要求,我們前面提到過,區(qū)塊高度大于等于227931的區(qū)塊BIP34應(yīng)該部署,且區(qū)塊的版本號(hào)應(yīng)該大于1;類似地,BIP66在區(qū)塊高度大于等于363725的區(qū)塊中已經(jīng)部署,要求區(qū)塊版本號(hào)不低于3,BIP65在區(qū)塊高度大于等于388381的區(qū)塊中部署,要求區(qū)塊版本號(hào)不低于4;
可以看到,相較于checkBlockHeaderSanity(),checkBlockHeaderContext()是檢查區(qū)塊頭中的難度值、時(shí)間戳、高度及版本號(hào)是否符號(hào)鏈上的要求。其中calcNextRequiredDifficulty()涉及到難度調(diào)整算法,我們來看看它的實(shí)現(xiàn):
//btcd/blockchain/difficulty.go
// calcNextRequiredDifficulty calculates the required difficulty for the block
// after the passed previous block node based on the difficulty retarget rules.
// This function differs from the exported CalcNextRequiredDifficulty in that
// the exported version uses the current best chain as the previous block node
// while this function accepts any block node.
//
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) calcNextRequiredDifficulty(lastNode *blockNode, newBlockTime time.Time) (uint32, error) {
// Genesis block.
if lastNode == nil {
return b.chainParams.PowLimitBits, nil
}
// Return the previous block's difficulty requirements if this block
// is not at a difficulty retarget interval.
if (lastNode.height+1)%b.blocksPerRetarget != 0 { (1)
// For networks that support it, allow special reduction of the
// required difficulty once too much time has elapsed without
// mining a block.
if b.chainParams.ReduceMinDifficulty {
......
}
// For the main network (or any unrecognized networks), simply
// return the previous block's difficulty requirements.
return lastNode.bits, nil (2)
}
// Get the block node at the previous retarget (targetTimespan days
// worth of blocks).
firstNode := lastNode
for i := int32(0); i < b.blocksPerRetarget-1 && firstNode != nil; i++ {
// Get the previous block node. This function is used over
// simply accessing firstNode.parent directly as it will
// dynamically create previous block nodes as needed. This
// helps allow only the pieces of the chain that are needed
// to remain in memory.
var err error
firstNode, err = b.index.PrevNodeFromNode(firstNode) (3)
if err != nil {
return 0, err
}
}
if firstNode == nil {
return 0, AssertError("unable to obtain previous retarget block")
}
// Limit the amount of adjustment that can occur to the previous
// difficulty.
actualTimespan := lastNode.timestamp - firstNode.timestamp
adjustedTimespan := actualTimespan (4)
if actualTimespan < b.minRetargetTimespan {
adjustedTimespan = b.minRetargetTimespan
} else if actualTimespan > b.maxRetargetTimespan {
adjustedTimespan = b.maxRetargetTimespan
}
// Calculate new target difficulty as:
// currentDifficulty * (adjustedTimespan / targetTimespan)
// The result uses integer division which means it will be slightly
// rounded down. Bitcoind also uses integer division to calculate this
// result.
oldTarget := CompactToBig(lastNode.bits)
newTarget := new(big.Int).Mul(oldTarget, big.NewInt(adjustedTimespan))
targetTimeSpan := int64(b.chainParams.TargetTimespan / time.Second)
newTarget.Div(newTarget, big.NewInt(targetTimeSpan)) (5)
// Limit new value to the proof of work limit.
if newTarget.Cmp(b.chainParams.PowLimit) > 0 {
newTarget.Set(b.chainParams.PowLimit) (6)
}
// Log new target difficulty and return it. The new target logging is
// intentionally converting the bits back to a number instead of using
// newTarget since conversion to the compact representation loses
// precision.
newTargetBits := BigToCompact(newTarget) (7)
......
return newTargetBits, nil
}
其主要步驟如下:
- 如果待加入?yún)^(qū)塊的高度在一個(gè)調(diào)整周期(2016個(gè)區(qū)塊)內(nèi),則期望的難度值就是父區(qū)塊的難度值,即不需要調(diào)整難度值,如代碼(1)、(2)處所示;
- 如果待加入?yún)^(qū)塊的高度是2016的整數(shù)倍,則需要進(jìn)行難度調(diào)整,首先找到上一次調(diào)整周期的起始區(qū)塊,它的高度也是2016的整數(shù)倍,如代碼(3)處所示;
- 代碼(4)處計(jì)算上一次調(diào)整周期內(nèi)經(jīng)過的時(shí)間差,即周期內(nèi)最后一個(gè)區(qū)塊與起始區(qū)塊的時(shí)間戳的差值,其值的有效范圍為3.5天到56天,如果限過這一范圍,則取其上限或下限;
- 代碼(5)處按如下公式計(jì)算新的難度值:

其中targetTimespan是14天,當(dāng)上一次調(diào)整周期(即currentTimespan)超過14天時(shí),說明“出塊”速度變慢,要降低難度值;當(dāng)currentTimespan小于14天時(shí),說明“出塊”速度過快,要增加難度值??梢钥闯?,困難調(diào)整算法就是為了穩(wěn)定“出塊”速度;
- 如果調(diào)整后的目標(biāo)難度值大于設(shè)定的上限,則將其值直接設(shè)為該限,即2^224 - 1。請(qǐng)注意,難度值越大,說明“出塊”的難度越小,這里的上限實(shí)際上是設(shè)定了“出塊”的最低難度;
- 最后調(diào)用BigToCompact()將目標(biāo)難度值編碼為難度Bits,如代碼(7)處所示;
在通過checkBlockContext()檢查區(qū)塊上下文通過之后,maybeAcceptBlock()將調(diào)用connectBestChain()將區(qū)塊寫入?yún)^(qū)塊鏈。
//btcd/blockchain/chain.go
// connectBestChain handles connecting the passed block to the chain while
// respecting proper chain selection according to the chain with the most
// proof of work. In the typical case, the new block simply extends the main
// chain. However, it may also be extending (or creating) a side chain (fork)
// which may or may not end up becoming the main chain depending on which fork
// cumulatively has the most proof of work. It returns whether or not the block
// ended up on the main chain (either due to extending the main chain or causing
// a reorganization to become the main chain).
//
// The flags modify the behavior of this function as follows:
// - BFFastAdd: Avoids several expensive transaction validation operations.
// This is useful when using checkpoints.
// - BFDryRun: Prevents the block from being connected and avoids modifying the
// state of the memory chain index. Also, any log messages related to
// modifying the state are avoided.
//
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, flags BehaviorFlags) (bool, error) {
fastAdd := flags&BFFastAdd == BFFastAdd
dryRun := flags&BFDryRun == BFDryRun
// We are extending the main (best) chain with a new block. This is the
// most common case.
if node.parentHash.IsEqual(&b.bestNode.hash) { (1)
// Perform several checks to verify the block can be connected
// to the main chain without violating any rules and without
// actually connecting the block.
view := NewUtxoViewpoint() (2)
view.SetBestHash(&node.parentHash) (3)
stxos := make([]spentTxOut, 0, countSpentOutputs(block))
if !fastAdd {
err := b.checkConnectBlock(node, block, view, &stxos) (4)
if err != nil {
return false, err
}
}
......
// Connect the block to the main chain.
err := b.connectBlock(node, block, view, stxos) (5)
if err != nil {
return false, err
}
// Connect the parent node to this node.
if node.parent != nil {
node.parent.children = append(node.parent.children, node)
}
return true, nil
}
......
// We're extending (or creating) a side chain which may or may not
// become the main chain, but in either case the entry is needed in the
// index for future processing.
b.index.Lock()
b.index.index[node.hash] = node (6)
b.index.Unlock()
// Connect the parent node to this node.
node.inMainChain = false
node.parent.children = append(node.parent.children, node)
......
// We're extending (or creating) a side chain, but the cumulative
// work for this new side chain is not enough to make it the new chain.
if node.workSum.Cmp(b.bestNode.workSum) <= 0 { (7)
......
return false, nil (8)
}
// We're extending (or creating) a side chain and the cumulative work
// for this new side chain is more than the old best chain, so this side
// chain needs to become the main chain. In order to accomplish that,
// find the common ancestor of both sides of the fork, disconnect the
// blocks that form the (now) old fork from the main chain, and attach
// the blocks that form the new chain to the main chain starting at the
// common ancenstor (the point where the chain forked).
detachNodes, attachNodes := b.getReorganizeNodes(node) (9)
// Reorganize the chain.
if !dryRun {
log.Infof("REORGANIZE: Block %v is causing a reorganize.",
node.hash)
}
err := b.reorganizeChain(detachNodes, attachNodes, flags) (10)
if err != nil {
return false, err
}
return true, nil
}
connectBestChain()的輸入是待加入?yún)^(qū)塊的blockNode對(duì)象和btcutil.Block輔助對(duì)象,輸出的第一個(gè)參數(shù)指明是否添加到主鏈,其主要步驟為:
- 如果父區(qū)塊的Hash就是主鏈?zhǔn)恰拔病眳^(qū)塊的Hash,則區(qū)塊將被寫入主鏈,如代碼(1)處所示;
- 創(chuàng)建一個(gè)UtxoViewpoint對(duì)象,并將view的觀察點(diǎn)設(shè)為主鏈的鏈尾,如代碼(2)、(3)處所示。UtxoViewpoint表示區(qū)塊鏈上從創(chuàng)世區(qū)塊到觀察點(diǎn)之間所有包含UTXO的交易及其中的UTXO(s)的集合,值得注意的是,UtxoViewpoint不僅僅記錄了UTXO(s),而且記錄所有包含未花費(fèi)輸出的交易,它將用來驗(yàn)證重復(fù)交易、雙重支付等,它記錄的utxoset是保證各節(jié)點(diǎn)上區(qū)塊鏈一致性的重要對(duì)象,我們隨后詳細(xì)介紹它;
- 接下,通過countSpentOutputs()計(jì)算區(qū)塊內(nèi)所有交易花費(fèi)的交易輸出的個(gè)數(shù),即所有交易的總的輸入個(gè)數(shù),并創(chuàng)建一個(gè)容量為相應(yīng)大小的spentTxOut slice;
- 代碼(4)處調(diào)用checkConnectBlock()對(duì)區(qū)塊內(nèi)的交易作驗(yàn)證,并更新utxoset,我們隨后進(jìn)一步分析它的實(shí)現(xiàn);
- 交易驗(yàn)證通過后,代碼(5)處調(diào)用connectBlock()將區(qū)塊相關(guān)的信息寫入數(shù)據(jù)庫,真正實(shí)現(xiàn)將區(qū)塊寫入主鏈,我們隨后詳細(xì)分析它的實(shí)現(xiàn);
- 如果父區(qū)塊的Hash不是主鏈?zhǔn)恰拔病眳^(qū)塊的Hash,則區(qū)塊將被寫入側(cè)鏈,根據(jù)側(cè)鏈的工作量之各,側(cè)鏈有可能被調(diào)整為主鏈。無論側(cè)鏈?zhǔn)欠癖徽{(diào)整為主鏈,先將當(dāng)前區(qū)塊更新到內(nèi)存索引器blockIndex中,如代碼(6)處所示,因?yàn)楹罄m(xù)對(duì)區(qū)塊的處理需要通過blockIndex來查找區(qū)塊,如我們前面提到的通過PrevNodeFromBlock()來查找父區(qū)塊;如果區(qū)塊被寫入主鏈,connectBlock()也會(huì)將區(qū)塊更新到blockIndex中,我們隨后將會(huì)看到;
- 如果側(cè)鏈的工作量之和小于主鏈的工作量之和,則直接返回,如代碼(7)、(8)處所示,如果區(qū)塊的父區(qū)塊在主鏈上,則從當(dāng)前區(qū)塊開始分叉;如果區(qū)塊的父區(qū)塊不在主鏈上,則它擴(kuò)展了側(cè)鏈;
- 如果側(cè)鏈的工作量之和大于主鏈的工作量之和,則需要將側(cè)鏈調(diào)整為主鏈。首先,調(diào)用getReorganizeNodes()找到分叉點(diǎn)以及側(cè)鏈和主鏈上的區(qū)塊節(jié)點(diǎn),如代碼(9)處所示;
- 然后,調(diào)用reorganizeChain()實(shí)現(xiàn)側(cè)鏈變主鏈;
在checkConnectBlock()和connectBlock()中進(jìn)行交易驗(yàn)證或者utxoset更新時(shí)都要涉及到對(duì)UtxoViewpoint的操作,為了便于后續(xù)理解驗(yàn)證重復(fù)交易、雙重支付等過程,我們先來介紹UtxoViewpoint,其定義如下:
//btcd/blockchain/utxoviewpoint.go
type UtxoViewpoint struct {
entries map[chainhash.Hash]*UtxoEntry
bestHash chainhash.Hash
}
......
// UtxoEntry contains contextual information about an unspent transaction such
// as whether or not it is a coinbase transaction, which block it was found in,
// and the spent status of its outputs.
type UtxoEntry struct {
modified bool // Entry changed since load.
version int32 // The version of this tx.
isCoinBase bool // Whether entry is a coinbase tx.
blockHeight int32 // Height of block containing tx.
sparseOutputs map[uint32]*utxoOutput // Sparse map of unspent outputs.
}
......
type utxoOutput struct {
spent bool // Output is spent.
compressed bool // The amount and public key script are compressed.
amount int64 // The amount of the output.
pkScript []byte // The public key script for the output.
}
可以看到,UtxoViewpoint主要記錄了主鏈上交易的Hash與對(duì)應(yīng)的*UtxoEntry之間的映射集合,所以,UtxoEntry實(shí)際上代表一個(gè)包含了Utxo的交易,它的各字段意義如下:
- modified: 記錄UtxoEntry是否被修改過,主要是sparseOutputs是否有變動(dòng);
- Version: UtxoEntry對(duì)應(yīng)的交易的版本號(hào);
- isCoinBase: UtxoEntry對(duì)應(yīng)的交易是否是coinbase交易;
- blockHeight: UtxoEntry對(duì)應(yīng)的交易所在的區(qū)塊的高度;
- sparseOutputs: 交易的utxo的集合,其索引即交易輸出的序號(hào);
utxoOutput中各字段的意義如下:
- spent: 記錄utxo是否已經(jīng)被花費(fèi),如果已經(jīng)花費(fèi),將會(huì)從UtxoEntry中移除;
- compressed: utxo的輸出幣值與鎖定腳本是否是壓縮形式存儲(chǔ);
- amout: utxo的輸出幣值;
- pkScript: 輸入的鎖定腳本;
當(dāng)區(qū)塊加入主鏈時(shí),區(qū)塊中的交易(包括coinbase)產(chǎn)生的UTXO將被加入到UtxoViewpoint對(duì)應(yīng)的utxo集合中,交易的輸入將從utxo集合中移除,可以說,主鏈的狀態(tài)與utxo集合的狀態(tài)是緊密聯(lián)系的,區(qū)塊鏈狀態(tài)的一致性實(shí)質(zhì)上指的是主鏈和utxo集合的狀態(tài)的一致性。在后面的分析中我們還會(huì)看到,側(cè)鏈可能通過reorganize變成主鏈,那么區(qū)塊將被從主鏈上移除,這時(shí)它包含的所有交易中已經(jīng)花費(fèi)的交易輸出將重新變成utxo,并進(jìn)入utxo集合中,所以區(qū)塊在加入主鏈時(shí),它包含的交易的所有花費(fèi)的spentTxOut也會(huì)被記錄下來,并以壓縮的形式寫入數(shù)據(jù)庫。后面的分析將會(huì)涉及到utxoset的操作,我們通過如下示意圖直觀理解它們之間的聯(lián)系:

根據(jù)上圖,我們可以提前思考下如何驗(yàn)證重復(fù)交易和雙重支付。當(dāng)新的區(qū)塊欲加入主鏈時(shí),主鏈的狀態(tài)如圖中所示,即鏈上有些交易的輸出已經(jīng)被花費(fèi),有些交易的輸出還未被完全花費(fèi),未被完全花費(fèi)的交易在utxoset中通過untxentry記錄。要驗(yàn)證新的區(qū)塊中有無交易與主鏈上的交易重復(fù),可以通過查找區(qū)塊中的交易是否已經(jīng)在主鏈上來實(shí)現(xiàn),但該種方式無疑是耗時(shí)的,因?yàn)殡S著交易量的增加,主鏈上的交易會(huì)越來越多,每次驗(yàn)證區(qū)塊的時(shí)候都要搜索所有的交易不是一個(gè)好的方式。另一個(gè)思路是只驗(yàn)證新區(qū)塊中的交易是否在utxoset中,因?yàn)閡txoentry指向的交易肯定在主鏈上,而不在utxoset中的交易已經(jīng)被完全花費(fèi)(如果新區(qū)塊被寫入主鏈,則這些交易的確認(rèn)數(shù)至少為1),即使新區(qū)塊中有重復(fù)的交易,若這些交易因?yàn)楹罄m(xù)分叉而被從主鏈上移除,utxoset也不會(huì)有任何影響,因?yàn)檫@些交易本來就不在utxoset中,這就避免了文章中提到的利用重復(fù)交易使“可花費(fèi)交易變成不可花費(fèi)”的攻擊,這也是BIP30提議解決重復(fù)交易的方案: 只允許重復(fù)完全被花費(fèi)了的交易。同樣地,驗(yàn)證雙重支付時(shí),也是通過驗(yàn)證新區(qū)塊中的交易的輸入是否指向了utxoset中的uxtoentry來實(shí)現(xiàn)的。如果交易的輸入未指向utxoset中任何記錄,則說明它試圖花費(fèi)一個(gè)已經(jīng)花費(fèi)了的交易;如果交易的輸入指向了utxoset中的utxoentry,但其已經(jīng)被新區(qū)塊中排在前面的交易花費(fèi)掉了或者被“礦工”正在“挖”的區(qū)塊里的交易花費(fèi)掉了,則該交易也被認(rèn)為是在雙重支付;只有當(dāng)交易的輸入指向了utxoentry且其未被花費(fèi)才能通過雙重支付檢查。了解了驗(yàn)證重復(fù)交易和雙重支付的思路后,我們理解后續(xù)的代碼實(shí)現(xiàn)將變得容易一些。
接下來,我們繼續(xù)分析checkConnectBlock()的實(shí)現(xiàn):
//btcd/blockchain/validate.go
func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block, view *UtxoViewpoint, stxos *[]spentTxOut) error {
......
// BIP0030 added a rule to prevent blocks which contain duplicate
// transactions that 'overwrite' older transactions which are not fully
// spent. See the documentation for checkBIP0030 for more details.
//
// There are two blocks in the chain which violate this rule, so the
// check must be skipped for those blocks. The isBIP0030Node function
// is used to determine if this block is one of the two blocks that must
// be skipped.
//
// In addition, as of BIP0034, duplicate coinbases are no longer
// possible due to its requirement for including the block height in the
// coinbase and thus it is no longer possible to create transactions
// that 'overwrite' older ones. Therefore, only enforce the rule if
// BIP0034 is not yet active. This is a useful optimization because the
// BIP0030 check is expensive since it involves a ton of cache misses in
// the utxoset.
if !isBIP0030Node(node) && (node.height < b.chainParams.BIP0034Height) { (1)
err := b.checkBIP0030(node, block, view) (2)
if err != nil {
return err
}
}
// Load all of the utxos referenced by the inputs for all transactions
// in the block don't already exist in the utxo view from the database.
//
// These utxo entries are needed for verification of things such as
// transaction inputs, counting pay-to-script-hashes, and scripts.
err := view.fetchInputUtxos(b.db, block) (3)
if err != nil {
return err
}
// BIP0016 describes a pay-to-script-hash type that is considered a
// "standard" type. The rules for this BIP only apply to transactions
// after the timestamp defined by txscript.Bip16Activation. See
// https://en.bitcoin.it/wiki/BIP_0016 for more details.
enforceBIP0016 := node.timestamp >= txscript.Bip16Activation.Unix() (4)
// The number of signature operations must be less than the maximum
// allowed per block. Note that the preliminary sanity checks on a
// block also include a check similar to this one, but this check
// expands the count to include a precise count of pay-to-script-hash
// signature operations in each of the input transaction public key
// scripts.
transactions := block.Transactions()
totalSigOps := 0
for i, tx := range transactions { (5)
numsigOps := CountSigOps(tx)
if enforceBIP0016 {
// Since the first (and only the first) transaction has
// already been verified to be a coinbase transaction,
// use i == 0 as an optimization for the flag to
// countP2SHSigOps for whether or not the transaction is
// a coinbase transaction rather than having to do a
// full coinbase check again.
numP2SHSigOps, err := CountP2SHSigOps(tx, i == 0, view) (6)
if err != nil {
return err
}
numsigOps += numP2SHSigOps
}
// Check for overflow or going over the limits. We have to do
// this on every loop iteration to avoid overflow.
lastSigops := totalSigOps
totalSigOps += numsigOps
if totalSigOps < lastSigops || totalSigOps > MaxSigOpsPerBlock { (7)
str := fmt.Sprintf("block contains too many "+
"signature operations - got %v, max %v",
totalSigOps, MaxSigOpsPerBlock)
return ruleError(ErrTooManySigOps, str)
}
}
// Perform several checks on the inputs for each transaction. Also
// accumulate the total fees. This could technically be combined with
// the loop above instead of running another loop over the transactions,
// but by separating it we can avoid running the more expensive (though
// still relatively cheap as compared to running the scripts) checks
// against all the inputs when the signature operations are out of
// bounds.
var totalFees int64
for _, tx := range transactions {
txFee, err := CheckTransactionInputs(tx, node.height, view, (8)
b.chainParams)
if err != nil {
return err
}
// Sum the total fees and ensure we don't overflow the
// accumulator.
lastTotalFees := totalFees
totalFees += txFee
if totalFees < lastTotalFees {
return ruleError(ErrBadFees, "total fees for block "+
"overflows accumulator")
}
// Add all of the outputs for this transaction which are not
// provably unspendable as available utxos. Also, the passed
// spent txos slice is updated to contain an entry for each
// spent txout in the order each transaction spends them.
err = view.connectTransaction(tx, node.height, stxos) (9)
if err != nil {
return err
}
}
// The total output values of the coinbase transaction must not exceed
// the expected subsidy value plus total transaction fees gained from
// mining the block. It is safe to ignore overflow and out of range
// errors here because those error conditions would have already been
// caught by checkTransactionSanity.
var totalSatoshiOut int64
for _, txOut := range transactions[0].MsgTx().TxOut {
totalSatoshiOut += txOut.Value
}
expectedSatoshiOut := CalcBlockSubsidy(node.height, b.chainParams) + (10)
totalFees
if totalSatoshiOut > expectedSatoshiOut {
str := fmt.Sprintf("coinbase transaction for block pays %v "+
"which is more than expected value of %v",
totalSatoshiOut, expectedSatoshiOut)
return ruleError(ErrBadCoinbaseValue, str)
}
// Don't run scripts if this node is before the latest known good
// checkpoint since the validity is verified via the checkpoints (all
// transactions are included in the merkle root hash and any changes
// will therefore be detected by the next checkpoint). This is a huge
// optimization because running the scripts is the most time consuming
// portion of block handling.
checkpoint := b.LatestCheckpoint()
runScripts := !b.noVerify
if checkpoint != nil && node.height <= checkpoint.Height {
runScripts = false (11)
}
......
// Enforce CHECKSEQUENCEVERIFY during all block validation checks once
// the soft-fork deployment is fully active.
csvState, err := b.deploymentState(node.parent, chaincfg.DeploymentCSV) (12)
if err != nil {
return err
}
if csvState == ThresholdActive {
// If the CSV soft-fork is now active, then modify the
// scriptFlags to ensure that the CSV op code is properly
// validated during the script checks bleow.
scriptFlags |= txscript.ScriptVerifyCheckSequenceVerify
// We obtain the MTP of the *previous* block in order to
// determine if transactions in the current block are final.
medianTime, err := b.index.CalcPastMedianTime(node.parent)
if err != nil {
return err
}
// Additionally, if the CSV soft-fork package is now active,
// then we also enforce the relative sequence number based
// lock-times within the inputs of all transactions in this
// candidate block.
for _, tx := range block.Transactions() {
// A transaction can only be included within a block
// once the sequence locks of *all* its inputs are
// active.
sequenceLock, err := b.calcSequenceLock(node, tx, view, (13)
false)
if err != nil {
return err
}
if !SequenceLockActive(sequenceLock, node.height, (14)
medianTime) {
str := fmt.Sprintf("block contains " +
"transaction whose input sequence " +
"locks are not met")
return ruleError(ErrUnfinalizedTx, str)
}
}
}
// Now that the inexpensive checks are done and have passed, verify the
// transactions are actually allowed to spend the coins by running the
// expensive ECDSA signature check scripts. Doing this last helps
// prevent CPU exhaustion attacks.
if runScripts {
err := checkBlockScripts(block, view, scriptFlags, b.sigCache) (15)
if err != nil {
return err
}
}
// Update the best hash for view to include this block since all of its
// transactions have been connected.
view.SetBestHash(&node.hash) (16)
return nil
}
checkConnectBlock()是區(qū)塊加入主鏈前最后也是最復(fù)雜的檢查過程,其主要步驟為:
- 代碼(1)、(2)處根據(jù)BIP30的建議,檢查區(qū)塊中的交易是否與主鏈上的交易重復(fù)。交易是通過交易的Hash來標(biāo)識(shí)的,那么有相同Hash值的交易就是重復(fù)的交易。我們?cè)?a href="http://www.itdecent.cn/p/a0a54afe11c6" target="_blank">《Btcd區(qū)塊鏈協(xié)議消息解析》中介紹過,Tx的Hash實(shí)際上是整個(gè)交易結(jié)構(gòu)序列化后進(jìn)行兩次SHA256()后的結(jié)果,也就是對(duì)交易的版本號(hào)、輸入、輸出和LockTime進(jìn)行雙哈希的結(jié)果。由于coinbase交易沒有輸入,使之較其它交易而言更容易生成相同的Hash值,也就是說,攻擊者更容易構(gòu)造出相同的coinbase交易。如果攻擊者跟蹤從某個(gè)coinbase交易開始的交易鏈上的所有交易,通過復(fù)制交易中的鎖定腳本或者解鎖腳本,就有可能復(fù)制出這些交易。攻擊者可能復(fù)制出交易并將其打包進(jìn)一個(gè)區(qū)塊中,這個(gè)區(qū)塊隨后可能因?yàn)閰^(qū)塊鏈分叉而被從主鏈上移除。我們將在后面分析中看到,從主鏈上移除的區(qū)塊中的交易產(chǎn)生的utxo將被從utxoset中移除。偽造的重復(fù)的交易被移除后,將導(dǎo)致原始交易產(chǎn)生的utxo也被移除,因?yàn)閡xtoset中是通過交易的Hash來索引utxoentry的,重復(fù)的交易將指向同一個(gè)utxoentry。如果原始交易的utxoentry未被花費(fèi),則移除后將導(dǎo)致其被認(rèn)為已經(jīng)花費(fèi)了。前面我們提到過BIP30建議,只允許重復(fù)已經(jīng)完全花費(fèi)的交易。代碼(1)處針對(duì)區(qū)塊高度小于227931且除高度為91842和91880以外的區(qū)塊進(jìn)行BIP30檢查。高度大于227931的區(qū)塊已經(jīng)部署B(yǎng)IP34,即coibase的鎖定腳本中包含了區(qū)塊高度值,使得coinbase的Hash沖突的可能性變小,故不再做BIP30檢查。代碼(2)處checkBIP0030()的實(shí)現(xiàn)比較簡(jiǎn)單,即只有當(dāng)區(qū)塊中的所有交易不在utxoset中或者對(duì)應(yīng)的utxoentry的所有output均已經(jīng)花費(fèi)掉,才算通過檢查。
- 代碼(3)處調(diào)用UtxoViewpoint的fetchInputUtxos()將區(qū)塊中所有交易的輸入引用的utxo從db中加載到內(nèi)存中,我們隨后分析它的實(shí)現(xiàn);
- 代碼(4)處判斷區(qū)塊是否已經(jīng)支持P2SH(Pay to Script Hash)。BIP16定義了P2SH,它是一種交易腳本類型,其解鎖和鎖定腳有如下形式:
scriptSig: [signature] {[pubkey] OP_CHECKSIG}
scriptPubKey: OP_HASH160 [20-byte-hash of {[pubkey] OP_CHECKSIG} ] OP_EQUAL
我們?cè)?a href="http://www.itdecent.cn/p/1ae346edc13c" target="_blank">《曲線上的“加密貨幣”(一)》中介紹的腳本形式是P2PKH(Pay to Public Key Hash)腳本采用的形式。在P2SH腳本中,鎖定腳本(scriptPubKey)中的160位Hash值不再是公鑰的HASH160結(jié)果,而是一段更復(fù)雜的腳本的HASH160結(jié)果,這段更復(fù)雜的腳本即是贖回腳本,如:
{2 [pubkey1] [pubkey2] [pubkey3] 3 OP_CHECKMULTISIG} 或 {OP_CHECKSIG OP_IF OP_CHECKSIGVERIFY OP_ELSE OP_CHECKMULTISIGVERIFY OP_ENDIF}
當(dāng)支持多簽名支付或者條件支付時(shí),較P2PKH腳本而言,P2SH腳本中鎖定腳本占用的空間更小,相應(yīng)地utxoset所占用的內(nèi)存也更少。當(dāng)花費(fèi)P2SH交易時(shí),解鎖腳本提供簽名和序列化后的贖回腳本,也就是說P2SH將提供贖回腳本的義務(wù)從支付方轉(zhuǎn)移到了收款方。我們?cè)?a href="http://www.itdecent.cn/p/5ddbdb6a9843" target="_blank">《Btcd區(qū)塊鏈的構(gòu)建(二)》中提到過,每個(gè)區(qū)塊中的腳本操作符總個(gè)數(shù)不能超過20000個(gè),checkBlcokSanity()中只統(tǒng)計(jì)了區(qū)塊交易中的鎖定腳本和解鎖腳本中操作符的總個(gè)數(shù),對(duì)于P2SH腳本來說,解鎖腳本中還包含了序列化的贖回腳本,因而還需要統(tǒng)計(jì)贖回腳本中的操作符個(gè)數(shù),這也是這里要判斷是否支持P2SH腳本的原因,BIP16建議驗(yàn)證Apr 1 00:00:00 UTC 2012后的交易時(shí),將贖回腳本中的操作符個(gè)數(shù)計(jì)算在內(nèi);
- 代碼(5)-(7)處計(jì)算區(qū)塊中所有交易腳本中的操作符的個(gè)數(shù),并統(tǒng)計(jì)P2SH腳本中贖回腳本的操作符個(gè)數(shù),并檢查總的個(gè)數(shù)是否超過20000個(gè);
- 代碼(8)處調(diào)用CheckTransactionInputs()檢查雙重支付,交易的輸出額是否超過輸入額以及coinbase交易能否被花費(fèi)等等,并計(jì)算交易的費(fèi)用,我們隨后將進(jìn)一步分析;
- 代碼(9)處調(diào)用UtxoViewpoint的connectTransaction()方法,將交易中的輸入utxo標(biāo)記為已花費(fèi),并更新傳入的slice stxos,同時(shí),將交易的輸出添加到utxoset中,我們隨后進(jìn)一步分析它的實(shí)現(xiàn)。值得注意的是,此時(shí)spentTxOutputs和utxoset中已經(jīng)花費(fèi)的utxoentry還沒有最終更新,需要等到connectBlock()將區(qū)塊最終連入主鏈時(shí)更新并將最新狀態(tài)寫入數(shù)據(jù)庫;
- 在檢查完交易的輸入并計(jì)算出所有交易的費(fèi)用之和后,開始檢查coinbase交易的輸出總值是否超過預(yù)期值,防止“礦工”隨意偽造獎(jiǎng)勵(lì)。coinbase交易的輸出即是對(duì)“礦工”“挖礦”的獎(jiǎng)勵(lì),從代碼(10)處可以看出,預(yù)期值包含“挖礦”的“補(bǔ)貼”和區(qū)塊中所有交易的“費(fèi)用”之和,其中“補(bǔ)貼”值約4年減半,初始值約為50 BTC,現(xiàn)在約“挖礦”的“補(bǔ)貼”約為12.5 BTC。
- 在上述檢查均通過后,開始準(zhǔn)備對(duì)交易中的腳本進(jìn)行驗(yàn)證,這是一個(gè)相對(duì)耗時(shí)的操作,如果區(qū)塊的高度是在預(yù)置的最新的Checkpoints之下,那么可以跳過腳本檢查,將錯(cuò)誤發(fā)現(xiàn)推遲到下一個(gè)Checkpoint檢查點(diǎn)。如果交易有任何變化,則會(huì)影響區(qū)塊的Hash,并進(jìn)而改變下一個(gè)Checkpoint區(qū)塊的Hash,使得我們前面在checkBlockHeaderContext()中提到的驗(yàn)證Checkpoint的過程失敗??梢钥闯?,區(qū)塊必須有足夠多的確認(rèn)才能成為Checkpoint,當(dāng)前Btcd版本中,區(qū)塊必須至少有2016個(gè)確認(rèn)才有可能成為Checkpoint;
- 代碼(12)處調(diào)用deploymentState()查詢CSV的部署狀態(tài),如果已經(jīng)部署,則調(diào)用calcSequenceLock(),根據(jù)BIP[68]中的建議根據(jù)交易輸入的Sequence值來計(jì)算交易的相對(duì)LockTime值,并通過SequenceLockActive()檢查區(qū)塊的MTP和高度是否已經(jīng)超過交易的鎖定時(shí)間和鎖定高度,如果區(qū)塊的高度小于交易鎖定高度或者區(qū)塊的MTP小于交易鎖定時(shí)間,則交易不應(yīng)該被打包進(jìn)該區(qū)塊;
- 如果區(qū)塊在最新的Checkpoint區(qū)塊之后,則繼續(xù)進(jìn)行腳本較驗(yàn),這一過程在checkBlockScripts()中實(shí)現(xiàn),我們將在后文中介紹;
- 通過上述所有驗(yàn)證后,將UtxoViewpoint的觀察點(diǎn)更新為當(dāng)前區(qū)塊,因?yàn)殡S后該區(qū)塊的相關(guān)狀態(tài)會(huì)最終寫入?yún)^(qū)塊鏈;
從connectBestChain()的實(shí)現(xiàn)中我們知道,checkConnectBlock()通過后,會(huì)調(diào)用connectBlock()將區(qū)塊最終寫入主鏈并將相關(guān)狀態(tài)寫入數(shù)據(jù)庫。checkConnectBlock()中涉及到的步驟比較復(fù)雜,其中涉及到的檢查交易輸入、更新utxoset、檢查交易的相對(duì)鎖定時(shí)間等的具體實(shí)現(xiàn)我們將在下篇文章中展開分析;同時(shí),對(duì)于通過getReorganizeNodes()和reorganizeChain()將側(cè)鏈變主鏈的具體實(shí)現(xiàn)也一并在下篇文章《Btcd區(qū)塊鏈的構(gòu)建(四)》中介紹。