You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
typeBlockChainstruct {
// 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.CheckpointcheckpointsByHeightmap[int32]*chaincfg.Checkpoint//配置初始化的时候,构造map,key是高度,value是CheckPoint结构体,按高度查找检查点// there are more field of this struct...
}
typeParamsstruct {
// there are more field of this struct...// Checkpoints ordered from oldest to newest. Checkpoints []Checkpoint// there are more field of this struct...
}
// Ensure chain matches up to predetermined checkpoints.blockHash:=header.BlockHash()
if!b.verifyCheckpoint(blockHeight, &blockHash) {
str:=fmt.Sprintf("block at height %d does not match "+"checkpoint hash", blockHeight)
returnruleError(ErrBadCheckpoint, str)
}
verifyCheckPoint实现:
// verifyCheckpoint returns whether the passed block height and hash combination// match the checkpoint data. It also returns true if there is no checkpoint// data for the passed block height.func (b*BlockChain) verifyCheckpoint(heightint32, hash*chainhash.Hash) bool {
if!b.HasCheckpoints() {
returntrue
}
// Nothing to check if there is no checkpoint data for the block height.checkpoint, exists:=b.checkpointsByHeight[height]
if!exists {
returntrue
}
if!checkpoint.Hash.IsEqual(hash) {
returnfalse
}
log.Infof("Verified checkpoint at height %d/block %s", checkpoint.Height,
checkpoint.Hash)
returntrue
}
// IsCheckpointCandidate returns whether or not the passed block is a good// checkpoint candidate.//// The factors used to determine a good checkpoint are:// - The block must be in the main chain// - The block must be at least 'CheckpointConfirmations' blocks prior to the// current end of the main chain// - The timestamps for the blocks before and after the checkpoint must have// timestamps which are also before and after the checkpoint, respectively// (due to the median time allowance this is not always the case)// - The block must not contain any strange transaction such as those with// nonstandard scripts//// The intent is that candidates are reviewed by a developer to make the final// decision and then manually added to the list of checkpoints for a network.
// CheckpointConfirmations is the number of blocks before the end of the current// best block chain that a good checkpoint candidate must be.constCheckpointConfirmations=2016// A checkpoint must be at least CheckpointConfirmations blocks// before the end of the main chain.mainChainHeight:=b.bestChain.Tip().heightifnode.height> (mainChainHeight-CheckpointConfirmations) {
returnfalse, nil
}
BTC 检查点(CheckPoint)调研报告
BTC源码
BTC官方源码是C++写的,但是有一个官方认可的全节点是golang写的,叫btcd。
此调研报告是基于btcd源码的调研。毕竟golang的源码可读性高太多了,C++短期一下子可能搞不明白。
BTCD检查点的实现
检查点的组织结构
检查点的实现源码文件叫checkpoints.go,在blockchain目录下。
CheckPoint被描述为一个块高度和块Hash的二元组结构体:
该结构体对象出现在BlockChain结构体中,为该类的一个属性,从目录结构和代码组织结构上看,CheckPoint就是主链的固有属性。
注意看checkpoint的类型,是在chaincfg模块里面的,跳转过去是params.go里面实现的,配置的参数大部分硬编码在这文件里:
硬编码在Params的结构体里:
从上面可以看到,是个CheckPoint的结构体数组,已排好序的,按照最老的到最新的区块排序。
这个配置参数结构体的值,是被硬编码设置的,主网,测试网都分别硬编码了属于自己的配置,下面只看主网的配置,重点看CheckPoint的配置:
所以从以上知道,节点在运行起来的初始化的时候,检查点列表就被固定了,用高度作为map索引,加快检查点的查找。所有逻辑都是围绕这个两个数据结构来的。
下面我们来看checkpoints.go里面实现了检查点相关的什么函数。
接下来我们再继续分析这些检查点相关的函数的调用栈,也就是说被哪些模块调用了,哪些模块会需要检查点,以及业务上如何处理的:
该函数基本受checkpoints内部的函数(LatestCheckpoint,verifyCheckpoint,findPreviousCheckpoint)调用。不是重要关注点。
BlockChain::isCurrent() -> LatestCheckpoint() 当前链的最高高度与块检查点最高的高度比较,如果小,isCurrent就返回false,暂时不是我们的关注点
BlockChain::checkConnectBlock() -> LatestCheckpoint() 如果当前链的最高高度的块与块检查点最高的高度比较,如果小于等于就关闭BTC里面的运行脚本(runscript),显然也不是我们的关注点
BlockChain::ProcessBlock -> BlockChain::maybeAcceptBlock -> BlockChain::checkBlockContext -> BlockChain::checkBlockHeaderContext -> BlockChain::verifyCheckpoint()
首先来说下上面的调用栈,ProcessBlock可以认为是P2P网络同步过来的区块,就进入到ProcessBlock方法里面。经过一些上下文独立的检验后,认为可能可以接受这个块进入链了,就会调用maybeAcceptBlock,而该函数里面还会有一些校验,比如校验该块的前序块,校验通过才会继续调用checkBlockContext继续往下校验。checkBlockContext首先会调用checkBlockHeaderContext来校验“块头部”上下文,如果校验出错,直接返回错误,checkBlockHeaderContext函数从块头拿到块的Hash,并拿到校验块的高度,调用verifyCheckpoint函数校验检查点的Hash是否匹配,如果不匹配就返回错误,如果找不到,就返回正确,继往下走。
以下是该函数调用后的所做的相关业务逻辑处理的代码:
verifyCheckPoint实现:
看以上代码就知道,几乎是很简单的检查点匹配判断,然后报错。
BlockChain::ProcessBlock -> BlockChain::findPreviousCheckpoint()
BlockChain::verifyCheckpoint() -> BlockChain::findPreviousCheckpoint()
以下就是各个函数调用findPreviousCheckPoint的业务处理
意思是这样,如果找不到前序检查点关联的块实体那么肯定是错的,因为没有检查点关联的块,你的链就相当于不是我这条链了,错误。
找到前序检查点关联的块实体,还要确定当前块的时间戳是大于前序检查点的时间戳的,不然也有问题,我作为后续的块的时间戳不应该比前序的时间戳小。这样不符合逻辑。
检查点的选取规则
根据之前的小节分析,检查点是通过一个额外的工具分析出来的,然后选取出来再逐步添加进主链中硬编码,随着版本的发布新增检查点硬编码,
以下是btcd的注释文档:
这里主要理解检查点选取的逻辑。主要是IsCheckpointCandidate(block *btcutil.Block) (bool, error)这个函数判断的,然后我们逐步分析它的调用栈。
先看它的实现,实现代码暂时不贴了,主要分析作为候选检查点的区块的必要条件:
检查点必须在主链上,因为链会硬分叉,说通俗点就是检查点必须在当前最长链上
检查点的高度必须与检查点所对应的Hash的块高一致
检查点的块必须要至少有一定的确认数,btcd里面是至少2016次确认数的块才能入选
main() -> findCandidates() -> IsCheckpointCandidate()
以上就是IsCheckpointCandidate的调用栈,用工具筛选出,然后再公布出来大家一起讨论筛选出检查点,最后再硬编码进主链的代码里。
BigbangCore的检查点方案
筛选检查点的方法为了方便,暂时可以不用做成额外的工具,可以做成RPC列出来,然后人工筛选。或者初期主链高度不高,手工筛选都可以,反正高度都是跳着来的,也不多,人力可以完成。
检查点列表也硬编码进代码里。
仿照BTCD,主要检查点检查的调用也只处理P2P的块,就不用检查已同步的块的了,因为最新的检查点的块的Hash和高度确定,就代表它之前的所有区块的Hash,Timestamp确定了。也就是说,最终如果不匹配还是会报错
参考资料
The text was updated successfully, but these errors were encountered: