validation.cpp

由 net_processing.cpp ProcessMessage 進入

  • net_processing.cpp / ProcessMessage --> ProcessNewBlock

3177L

  • bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr pblock, bool fForceProcessing, bool *fNewBlock)
    • AcceptBlock
    • ActivateBestChain
    • chainparams.GetConsensus()
    • CheckBlock
    • CheckBlockIndex
    • GetMainSignals().BlockChecked
    • error
    • GetMainSignals
    • NotifyHeaderTip

3098L

  • static bool AcceptBlock(const std::shared_ptr& pblock, CValidationState& state, const CChainParams& chainparams, CBlockIndex* ppindex, bool fRequested, const CDiskBlockPos dbp, bool* fNewBlock)


2428L

  • bool ActivateBestChain(CValidationState &state, const CChainParams& chainparams, std::shared_ptr pblock)

Make the best chain active, in multiple steps. The result is either failure or an activated best chain. pblock is either nullptr or a pointer to a block that is already loaded (to avoid loading it again from disk).

Note that while we're often called here from ProcessNewBlock, this is far from a guarantee. Things in the P2P/RPC will often end up calling us in the middle of ProcessNewBlock - do not assume pblock is set sanely for performance or correctness!


4062L

  • void static CheckBlockIndex(const Consensus::Params& consensusParams)

During a reindex, we read the genesis block and call CheckBlockIndex before ActivateBestChain, so we have the genesis block in mapBlockIndex but no active chain. (A few of the tests when iterating the block tree require that chainActive has been initialized.)

Unsolicited block validation

Message received: Block(NetMsgType::BLOCK) in net_processing.cpp [see Data Messages)]

礦工將挖到的交易打包成新的區塊後,會廣播 type BLOCK 的訊息給 P2P 網路中的節點,各節點收到 BLOCK 訊息之後會進行區塊的各項驗證以決定是否可以放到鏈上,此驗證過程可大致分為三大階段:驗證新區塊、儲存區塊、挑選最長鏈

0. 前置 - 確認是否已收到區塊

此為網路層 (net_processing.cpp) 的前置確認,從 in flight queue (map) 中查詢是否已有此區塊的 hash,不論存在與否都會將結果傳至接下來的步驟:ProcessNewBlock (validation.cpp)

1. 驗證新區塊

Source: CheckBlock ( validation.cpp )

  • 驗證區塊

    進入 merkle.cpp, 計算 Merkle Root

    • 驗證變種 Merkle Tree

    Merkle tree 的風險,詳見 merkle.cpp 的頂端註解,或 google CVE-2012-2459

    • 驗證區塊大小

    回到 validation.cpp

    • 驗證只有第一筆 Transaction 是 coinbase,其餘皆不能是
    • 驗證區塊裡的 Transactions

    進入 tx_verify.cpp

    • vin, vout 不能是空的
    • block size 未超過限制
    • vout 不能是負的或超出限制
    • vin 不能重覆 (double spending?,因為這個很耗時,這裡被 skip 掉)
    • scriptSig 的 size 不能超出限制 (coinbase only)
    • 前一筆交易的 vout 不能是空的 (for all transactions not coinbase)
    • 驗證 ScriptSig OpCode 的數量
下圖為此階段的流程示意圖

2. 儲存區塊

Source: AcceptBlock ( validation.cpp )

  • 保存驗證 Block Header 後的新區塊

    AcceptBlockHeader,取新區塊及前一區塊的 header 再做驗證,成功後紀錄至 mapBlockIndex 中,並更新 pindex

    • 確認 mapBlockIndex 裡是否已存過此 block (check duplicate)

      • (*)當不為 GenesisBlock 時,如果存過就驗證其合法性,並回傳此區塊的 pointer or error
      • 確認 block header,同之前驗證區塊 header的方法
      • 驗證前一個區塊是否存在?是否合法?以及 Header是否合法?

        ContextualCheckBlockHeader,context 是指只針對 header,而非 UTXO,因為 UTXO 已在 ConnectBlock() 驗證過了

        Header 的檢查包含了 POW, checkpoints (不允許 fork), 新區塊的 timestamp 須在前一個之後,且不能和現在的時間差異太大

    • 新區塊資訊加入 mapBlockIndex

      AddToBlockIndex,會再檢查一次是否有重覆,再把 block 資訊包成 CBlockIndex 的類型

      • 檢查 mapBlockIndex 裡是否有重覆 block
      • 查找 mapBlockIndex 裡是否有此 block 的前一個區塊,並做關聯(point to)
      • 計算 POW

        前一區塊POW (or 0) + 此區塊POW ( GetBlockProof )

      • 更新 BestHeader

        pindexBestHeader

      • 紀錄此變動 ( change log ? )

        加入至 setDirtyBlockIndex

    • 檢查 mapBlockIndex

      CheckBlockIndexmapBlockIndex 保存了至今整個的 block tree

      • Genesis block 只有一個
      • DFS 遍歷整個 block tree,針對每個 block

        • 如果為 Genesis Block,須與最長鍊的 Genesis 相同
        • 確認是否有 Transaction 資料及是否合法
        • Parent blocks 是否已被處理過
        • 確認區塊高度和目前 trace 的高度是否一致
        • POW > parent POW
        • TREE valid, CHAIN valid, SCRIPTS valid,這些隱含其 parents 皆是, and BLOCK valid
        • If block >= 當前最高區塊 (tip) 的 work 或 <= 當前最高區塊 (tip) 的 received time ( see CBlockIndexWorkComparator ),且為 block valid 和其 parents 的資料我們都有,則必在 setBlockIndexCandidates 之中;反之,如果有任何 parent 遺漏,則必在 mapBlocksUnlinked,代表有 parent 已經被移除了 (因為不在最長鏈上?)
        • 確認是否在 mapBlocksUnlinked 之中,可能條件
        • (*)如果不是 leaf node 進入下一層
        • ()如果為 leaf node,則移到 parent or sibling ( **continue loop* ) 去確認是否已檢查過 the last child
      • 確認 traverse 了整個 map

  • 針對更新後的 pindex 進行檢查,略過

    1. 已存在此區塊
    2. 如果不是我們想要處理的 ( !fRequested , 可能為來源 node 是在黑名單,尚未看到此情形 )
      • 已處理過,現在被刪除的區塊
      • work 比當前 chain 小的
      • 區塊高度太高
  • 再次檢查區塊

    如驗證不過,則將 block 設成 failed,紀錄在 setDirtyBlockIndex (change log?) 中

    • 同之前 CheckBlock 方法
    • 隔離驗證 ContextualCheckBlock

      • BIP113

        改用前 11 個區塊的 timestamp 中位數 ( median,原本是用當前區塊的 timestamp ) 來和 locktime 做比較,以避免有心人士特意欺騙他當前所包的 block 時間來賺取更多獎勵

      • 所有 Transaction 已完成

        無 locktime 或已過 locktime 如果 locktime 未過,是否簽名者(們)同意更新 transaction 所有 input 的 sequence number 至 maximum (0xffffffff),當 sequence number 為 maximum 時,則 locktime 即失效 [詳細說明]

      • BIP34

      • 驗證簽名? (witness commitments)
      • 驗證 Block Weight
  • 如果是接在最長鏈上,RELAY...

    GetMainSignals().NewPoWValidBlock(pindex, pblock)

  • 將 block 寫到 history 檔案

  • 更新 disk 上的 chain state

    FlushStateToDisk

2.1 檢查及通知

在更新完 disk 上的 block 檔案後,會再檢查 mapBlockIndex,並透過 [boost signal] 通知更新失敗 or 通知更新最高區塊 (tip) 的 header ( NotifyHeaderTip )

3. 挑選最長鏈

ActivateBestChain

results matching ""

    No results matching ""