func (bc *BlockChain) NewBlockHandler(payload []byte, peer peer.ID) error {
该函数是对新的区块的数据处理,payload
是一个字节切片,表示新块的数据;peer
是一个peer.ID
类型的变量,表示发送新块数据的对等节点。:
err := proto.Unmarshal(payload, &nweBlock)
将payload
反序列化为nweBlock
对象,如果有错误err,就输出错误日志并返回errblock.FromProtoMessage
方法将nweBlock
中的新块数据转换为block
对象。block.FromProtoMessage
方法将nweBlock
中的新块数据转换为block
对象bc.chBlocks <- &block
将block
对象发送到bc.chBlocks
通道中。nil
。func (bc *BlockChain) SetEngine(engine consensus.Engine) {
bc.engine = engine
}
用于设置区块链的engine入口
func (bc *BlockChain) SealedBlock(b block2.IBlock) error {
pbBlock := b.ToProtoMessage()
return bc.p2p.Broadcast(context.TODO(), pbBlock)
}
将块信息转换为message广播出去,通过p2p协议的bc.p2p.Broadcast(context.TODO(), pbBlock)
进行
func (bc *BlockChain) StopInsert() {
atomic.StoreInt32(&bc.procInterrupt, 1)
}
atomic.StoreInt32(&bc.procInterrupt, 1)
是Go语言中的一个原子操作,用于将整数1存储到变量bc.procInterrupt
的内存地址中。用于中断插入进程。
func (bc *BlockChain) insertStopped() bool {
return atomic.LoadInt32(&bc.procInterrupt) == 1
}
atomic.LoadInt32(&bc.procInterrupt)
是Go语言中的一个原子操作,用于从变量bc.procInterrupt
的内存地址中加载整数。insertStopped
返回 true
表示在调用 StopInsert
之后,插入insertStopped
返回 true
表示在调用 StopInsert
之后,插入操作已经停止。func (bc *BlockChain) InsertChain(chain []block2.IBlock) (int, error) {
该函数用于插入区块,是一个很重要的函数
if block.Number64().Cmp(uint256.NewInt(0).Add(prev.Number64(), uint256.NewInt(1))) != 0 || block.ParentHash() != prev.Hash() {
bc.lock.Lock()
defer bc.lock.Unlock()
,确保在函数完成之后会释放锁bc.insertChain(chain)
进行插入(参考1.7)func (bc *BlockChain) insertChain(chain []block2.IBlock) (int, error) {
这也是一个很重要的函数
bc.insertStopped()
,如果有,就直接返回0和nilblock.Header()和true
)bc.engine.VerifyHeaders
对区块的头部和seals进行验证,并返回abort和reaultsdefer close(abort)
在函数完成的时候关闭通道block, err := it.next()
//next返回迭代器中的下一个块,以及该块的任何潜在验证错误。当到达终点时,它将返回(nil,nil)。bc.skipBlock(err)
(参考2.4),如果错误是已知的,就会执行该if语句bc.forker.ReorgNeeded
(参考1.15)返回是否应基于给定的外部标头和本地规范链应用reorgfor block != nil && bc.skipBlock(err) {
log.Debug("Writing previously known block", "number", block.Number64(), "hash",block.Hash())
if err := bc.writeKnownBlock(nil, block); err != nil {
return it.index, err
}
lastCanon = block
block, err = it.next()
}
writeKnownBlock使用已知块更新头块标志,并在必要时引入chain reorg
10. 进行switch语句选择,根据不同的错误类型进行不同的操作
errors.Is(err, ErrPrunedAncestor)
,则将其插入为侧链bc.insertSideChain(block, it)
,并在TD增长到足够大时进行重组织。errors.Is(err, ErrFutureBlock)
或者错误是ErrUnknownAncestor(errors.Is(err, ErrUnknownAncestor)
且未来区块包含当前区块的父哈希bc.futureBlocks.Contains(it.first().ParentHash())
,则将该区块及其所有子区块推迟到未来队列中(未知祖先)err := bc.AddFutureBlock(block)
,同时根据迭代器修改stats中的忽视数量和queued数量bc.futureBlocks.Remove(block.Hash())
,增加忽略的区块数量,并报告该区块的错误信息bc.reportBlock(block, nil, err)
。最后返回当前索引和错误信息。ctx
:一个上下文对象,用于传递额外的信息给函数。db
:一个读写数据库对象,用于执行数据库操作。blockNr
:一个无符号64位整数,表示区块编号。f
:一个函数,接受四个参数:事务对象tx
、内部区块状态对象ibs
、状态读取器对象reader
和状态写入器对象writer
。该函数返回两个值:一个映射表和一个错误对象。db.BeginRo(ctx)
,如果发生错误则返回空指针和错误对象state.NewPlainStateReader(tx)
state.New(stateReader)
state.NewNoopWriter()
f
,并将事务对象、内部区块状态对象、状态读取器对象和状态写入器对象作为参数传递给它nopay, err = f(tx, ibs, stateReader, stateWriter)
。将返回的结果赋值给变量nopay
和err
。nopay
映射表和nil
错误对象。当区块不为nil并且没有错误或者错误为ErrKnownBlock
的时候,不断遍历blocks:
start := time.Now()
,创建变量收据、日志和gasrawdb.ReadHeader(tx, hash, number)
获取到对应的区块头GetHashFn(block.Header().(*block2.Header), getHeader)
返回对应的hash(参考Ⅰ)bc.process.Process
方法来处理区块,并返回一些结果,包括收据、未支付的交易、日志、使用的gas以及可能的错误。如果处理过程中出现错误,它会报告这个错误并返回nil和errvstart := time.Now()
bc.validator.ValidateState
方法来验证区块的状态。如果验证失败,它会报告这个错误bc.reportBlock(block, receipts, err)
并返回evmRecord
的过程中存在err,就返回当前的index和errwstart := time.Now()
status, err = bc.writeBlockWithState(block, receipts, ibs, nopay)
来写块,有错就返回len(logs) > 0
,则发送全局日志事件event.GlobalEvent.Send
。最后将最后一个规范块设置为当前块lastCanon = block
。ErrFutureBlock
的时候,我们考虑未来区块bc.AddFutureBlock(block)
,有错的时候就返回ErrUnknownAncestor
的时候,不断遍历获取下一区块bc.AddFutureBlock(block)
,有错就返回func (bc *BlockChain) insertSideChain(block block2.IBlock, it *insertIterator) (int, error) {
与1.7不同,该函数用于插入侧链
ErrPrunedAncestor
的时候,不断调用block.next()来获取下一块进行for循环bc.GetBlockByNumber(number)
,有错就返回0和nilpt := bc.GetTd(block.Hash(), block.Number64())
externTd = *pt
(3)如果给定区块不是主链上的区块,但与主链上的一个区块具有相同的状态根值canonical.StateRoot() == block.StateRoot()
,那么它很可能是一个shadow-state攻击。当一个分叉被导入到数据库中,并最终达到一个没有被修剪的高度时,我们会发现状态已经存在。这意味着侧链区块引用了一个已经存在于我们的主链中的状态。如果不及时进行检查,我们将在不验证之前区块状态的情况下导入区块。在这种情况下,代码会返回一个错误信息,表示检测到了侧链攻击。
externTd.Cmp(uint256.NewInt(0)) == 0
:判断 externTd 是否等于0。如果等于0,说明当前区块是创世区块,没有前一个区块,因此需要特殊处理。通过bc.GetTd
和block.ParentHash()
获取父区块的TD值并将将父区块的TD值赋给 externTd。externTd = *externTd.Add(&externTd, block.Difficulty())
:将当前区块的难度值与父区块的TD值相加,得到当前区块的总难度值,并更新 externTdbc.WriteBlockWithoutState(block)
方法将区块写入区块链,如果写入过程中出现错误,返回当前索引和错误信息bc.forker.ReorgNeeded
(参考1.15)判断是否需要reorg,如有错就返回当前索引值index和errbc.GetTd
获得难度td并赋值给localtd,输出日志信息并返回当前索引index和errparent := it.previous()
获得前一区块为父区块!bc.HasState(parent.StateRoot())
,执行for循环:bc.GetHeader
向前遍历父区块bc.GetBlock
,遍历地加入创建地遍历blocks中:if len(blocks) >= 2048
,我们仍然继续导入,但是我们需要输出一个提示日志func (bc *BlockChain) recoverAncestors(block block2.IBlock) (types.Hash, error) {
这段代码是一个名为recoverAncestors
的函数,它属于一个名为BlockChain
的结构体。该函数的作用是恢复给定区块的所有祖先区块,并将它们插入到区块链中。函数返回两个值:一个是类型为types.Hash
的哈希值,表示最后一个成功插入的祖先区块的哈希值;另一个是错误信息,如果发生错误则返回非空的错误对象。
bc.GetBlock
,将父区块的哈希、区块号依次加入,直到找到具有有效状态的父区块bc.HasState(parent.Hash())
或到达区块链的起始点parent == nil
bc.GetBlock
,并将其插入到区块链中bc.insertChain
(参考1.7)。如果插入过程中发生错误,函数将返回当前区块的父哈希值和错误信息nil
作为错误信息,表示所有祖先区块已成功插入到区块链中func (bc *BlockChain) WriteBlockWithoutState(block block2.IBlock) (err error) {
WriteBlockWithoutState仅将块及其元数据写入数据库,但不写入任何状态。 这用于构建竞争方叉,直到超过规范总难度。
bc.insertStopped()
,就返回errInsertionInterrupted
错误bc.ChainDB.Update
,其中写数据库调用rawdb包中的rawdb.WriteBlock
进行,如果有错就返回errfunc (bc *BlockChain) WriteBlockWithState(block block2.IBlock, receipts []*block2.Receipt, ibs *state.IntraBlockState, nopay map[types.Address]*uint256.Int) error {
该函数WriteBlockWithState将块和所有关联状态写入数据库
bc.writeBlockWithState
(参考1.12)来写入数据库func (bc *BlockChain) writeBlockWithState(block block2.IBlock, receipts []*block2.Receipt, ibs *state.IntraBlockState, nopay map[types.Address]*uint256.Int) (status WriteStatus, err error) {
该函数WriteBlockWithState将块和所有关联状态写入数据库
函数的参数包括:
block
:要写入的区块对象,类型为block2.IBlock
。receipts
:与该区块关联的交易收据列表,类型为[]*block2.Receipt
。ibs
:内部区块状态对象,类型为*state.IntraBlockState
。nopay
:一个映射表,表示不需要支付奖励的地址及其对应的奖励值,类型为map[types.Address]*uint256.Int
。函数的返回值包括:
status
:写入状态,类型为WriteStatus
。可能的值有NonStatTy
(非正常状态)、CanonStatTy
(正常状态)和SideStatTy
(侧链状态)。err
:错误信息,类型为error
。如果发生错误,将返回相应的错误信息。函数的主要逻辑如下:
bc.ChainDB.Update
方法更新区块链数据库,执行以下操作:
rawdb.ReadTd
,ptd,如果为nil就返回consensus.ErrUnknownAncestor
externTd
,并将其写入数据库rawdb.WriteTd
if len(receipts) > 0
,将其追加到数据库中rawdb.AppendReceipts
rawdb.WriteBlock
state.NewPlainStateWriter
提交区块ibs.CommitBlock
,并处理一些额外的操作,如写入更改集stateWriter.WriteChangeSets()
和历史记录stateWriter.WriteHistory()
rawdb.PutAccountReward
写入数据库。bc.forker.ReorgNeeded
(参考1.15),即当前区块是否是头区块的直接子区块。如果是,则进行重组织。NonStatTy
,没出错status就为CanonStatTy
。如果不需要reorg,status就为SideStatTy
bc.writeHeadBlock
(参考1.13),并保存最新的区块,在这个过程中出错的话就返回NonStatTy
bc.futureBlocks.Get(block.Hash())
,则从未来区块列表中移除它func (bc *BlockChain) writeHeadBlock(tx kv.RwTx, block block2.IBlock) error {
这段代码是一个名为writeHeadBlock
的函数,它的作用是将一个区块(block)写入区块链数据库。函数接收两个参数:一个是键值对读写事务(tx),另一个是要写入的区块(block)。
bc.ChainDB.BeginRw(bc.ctx)
并标记为非外部事务notExternalTx = true
,同时使用延迟回滚rawdb.WriteHeadBlockHash
rawdb.WriteTxLookupEntries
rawdb.WriteCanonicalHash
if notExternalTx
,则提交事务tx.Commit()
。如果在执行过程中出现错误,函数会返回相应的错误信息。func (bc *BlockChain) reportBlock(block block2.IBlock, receipts []*block2.Receipt, err error)
该函数用于bad区块的错误日志,遍历传入的收据列表,输出错误信息
func (bc *BlockChain) ReorgNeeded(current block2.IBlock, header block2.IBlock) bool {
switch current.Number64().Cmp(header.Number64()) {
case 1:
return false
case 0:
return current.Difficulty().Cmp(uint256.NewInt(2)) != 0
}
return true
}
该函数用于判断是否需要进行reorg重组织
使用switch语句来根据current和header的区块号进行不同的比较操作。
func (bc *BlockChain) SetHead(head uint64) error {
newHeadBlock, err := bc.GetBlockByNumber(uint256.NewInt(head))
if err != nil {
return nil
}
return bc.ChainDB.Update(bc.ctx, func(tx kv.RwTx) error {
return rawdb.WriteHeadHeaderHash(tx, newHeadBlock.Hash())
})
}
该方法的作用是将区块链的头部设置为指定的区块号。
bc.GetBlockByNumber
,有错就返回nilbc.ChainDB.Update
函数来更新区块链数据库。在更新过程中,它调用了rawdb包中的rawdb.WriteHeadHeaderHash
函数,将新的头部区块哈希值写入数据库func (bc *BlockChain) AddFutureBlock(block block2.IBlock) error {
AddFutureBlock检查该块是否在允许接受的最大窗口内以供将来处理,如果该块太超前且未添加,则返回错误。将一个区块添加到未来区块队列中。
max
,它是当前时间加上允许的最大未来区块时间maxTimeFutureBlocks
block.Time() > max
,如果是,则返回一个错误信息,表示未来区块的时间戳超过了允许的范围。block.Difficulty().Uint64() == 0
,如果是,则不将其添加到未来区块队列中,并返回nil
bc.futureBlocks.Add
,并返回nil
func (bc *BlockChain) HasBlockAndState(hash types.Hash, number uint64) bool {
该函数通过传入的hash和number来判断是否存在对应的区块
block := bc.GetBlock(hash, number)
来获取对应的区块block(参考Ⅰ)bc.HasState(block.Hash())
(参考2.2)判断是否有对应的statefunc (bc *BlockChain) HasState(hash types.Hash) bool {
bc.ChainDB.BeginRo(bc.ctx)
开启一个事务rawdb.IsCanonicalHash(tx, hash)
函数来判断有没有,有错err就返回falsedefer tx.Rollback()
func (bc *BlockChain) HasBlock(hash types.Hash, number uint64) bool {
该函数通过对应的hash和number来判断是否有对应的区块block
bc.blockCache.Contains(hash)
,如果存在就返回truebc.ChainDB.View
,其中调用数据库包rawdb中的rawdb.HasHeader(tx, hash, number)
来进行判断,并将结果赋值给flagfunc (bc *BlockChain) skipBlock(err error) bool {
if !errors.Is(err, ErrKnownBlock) {
return false
}
return true
}
这段代码是一个名为 skipBlock
的函数,它接受一个错误参数 err。该函数的作用是检查传入的错误是否为已知的块错误(ErrKnownBlock
)。如果是已知的块错误,则返回 true,否则返回 false。