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 )
驗證區塊
- 驗證區塊 Header
- 驗證 POW
- 驗證 MerkleRoot
進入 merkle.cpp, 計算 Merkle Root
- 驗證變種 Merkle Tree
Merkle tree 的風險,詳見 merkle.cpp 的頂端註解,或 google CVE-2012-2459
- 驗證區塊大小
- 驗證只有第一筆 Transaction 是 coinbase,其餘皆不能是
- 驗證區塊裡的 Transactions
- 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
-
CheckBlockIndex,mapBlockIndex 保存了至今整個的 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 進行檢查,略過
- 已存在此區塊
- 如果不是我們想要處理的 ( !fRequested , 可能為來源 node 是在黑名單,尚未看到此情形 )
- 已處理過,現在被刪除的區塊
- work 比當前 chain 小的
- 區塊高度太高
再次檢查區塊
如驗證不過,則將 block 設成 failed,紀錄在 setDirtyBlockIndex (change log?) 中
- 同之前 CheckBlock 方法
隔離驗證 ContextualCheckBlock
-
改用前 11 個區塊的 timestamp 中位數 ( median,原本是用當前區塊的 timestamp ) 來和 locktime 做比較,以避免有心人士特意欺騙他當前所包的 block 時間來賺取更多獎勵
所有 Transaction 已完成
無 locktime 或已過 locktime 如果 locktime 未過,是否簽名者(們)同意更新 transaction 所有 input 的 sequence number 至 maximum (0xffffffff),當 sequence number 為 maximum 時,則 locktime 即失效 [詳細說明]
- 驗證簽名? (witness commitments)
- 驗證 Block Weight
-
- 如果是接在最長鏈上,RELAY...
將 block 寫到 history 檔案
計算 block serialize 後的大小
- 找到該寫入此 block 的檔案位置
- 將 block 寫到檔案中
- 將 block 設成已有 data 且已驗證過
up to BLOCK_VALID_TRANSACTIONS,ReceivedBlockTransactions
-
2.1 檢查及通知
在更新完 disk 上的 block 檔案後,會再檢查 mapBlockIndex,並透過 [boost signal] 通知更新失敗 or 通知更新最高區塊 (tip) 的 header ( NotifyHeaderTip )
3. 挑選最長鏈
更新直到當前的最高區塊 (tip) 位於最長鏈 (chainActive) 上
-
TODO: detail description
-
TODO: detail description
-
TODO: detail description
For each block, signal BlockConnected
- 通知外面的 listeners 新的最高區塊 (tip)
- 通知 UI
-
- 檢查 mapBlockIndex
更新 disk 的 chain state