Derek解读Bytom源码-孤块管理
作者:Derek
简介
Github地址:https://github.com/Bytom/bytom
Gitee地址:https://gitee.com/BytomBlockchain/bytom
本章介绍bytom代码孤块管理
作者使用MacOS操作系统,其他平台也大同小异
Golang Version: 1.8
孤块介绍
什么是孤块
当节点收到了一个有效的区块,而在现有的主链中却未找到它的父区块,那么这个区块被认为是“孤块”。父区块是指当前区块的PreviousBlockHash字段指向上一区块的hash值。
接收到的孤块会被存储在孤块池中,直到它们的父区块被节点收到。一旦收到了父区块,节点就会将孤块从孤块池中取出,并且连接到它的父区块,让它作为区块链的一部分。
孤块出现的原因
当两个或多个区块在很短的时间间隔内被挖出来,节点有可能会以不同的顺序接收到它们,这个时候孤块现象就会出现。
我们假设有三个高度分别为100、101、102的块,分别以102、101、100的颠倒顺序被节点接收。此时节点将102、101放入到孤块管理缓存池中,等待彼此的父块。当高度为100的区块被同步进来时,会被验证区块和交易,然后存储到区块链上。这时会对孤块缓存池进行递归查询,根据高度为100的区块找到101的区块并存储到区块链上,再根据高度为101的区块找到102的区块并存储到区块链上。
孤块源码分析
孤块管理缓存池结构体
protocol/orphan_manage.go
type OrphanManage struct {
orphan map[bc.Hash]*types.Block
prevOrphans map[bc.Hash][]*bc.Hash
mtx sync.RWMutex
}
func NewOrphanManage() *OrphanManage {
return &OrphanManage{
orphan: make(map[bc.Hash]*types.Block),
prevOrphans: make(map[bc.Hash][]*bc.Hash),
}
}
- orphan 存储孤块,key为block hash,value为block结构体
- prevOrphans 存储孤块的父块
- mtx 互斥锁,保护map结构在多并发读写状态下保持数据一致
添加孤块到缓存池
func (o *OrphanManage) Add(block *types.Block) {
blockHash := block.Hash()
o.mtx.Lock()
defer o.mtx.Unlock()
if _, ok := o.orphan[blockHash]; ok {
return
}
o.orphan[blockHash] = block
o.prevOrphans[block.PreviousBlockHash] = append(o.prevOrphans[block.PreviousBlockHash], &blockHash)
log.WithFields(log.Fields{"hash": blockHash.String(), "height": block.Height}).Info("add block to orphan")
}
当一个孤块被添加到缓存池中,还需要记录该孤块的父块hash。用于父块hash的查询
查询孤块和父孤块
func (o *OrphanManage) Get(hash *bc.Hash) (*types.Block, bool) {
o.mtx.RLock()
block, ok := o.orphan[*hash]
o.mtx.RUnlock()
return block, ok
}
func (o *OrphanManage) GetPrevOrphans(hash *bc.Hash) ([]*bc.Hash, bool) {
o.mtx.RLock()
prevOrphans, ok := o.prevOrphans[*hash]
o.mtx.RUnlock()
return prevOrphans, ok
}
删除孤块
func (o *OrphanManage) Delete(hash *bc.Hash) {
o.mtx.Lock()
defer o.mtx.Unlock()
block, ok := o.orphan[*hash]
if !ok {
return
}
delete(o.orphan, *hash)
prevOrphans, ok := o.prevOrphans[block.PreviousBlockHash]
if !ok || len(prevOrphans) == 1 {
delete(o.prevOrphans, block.PreviousBlockHash)
return
}
for i, preOrphan := range prevOrphans {
if preOrphan == hash {
o.prevOrphans[block.PreviousBlockHash] = append(prevOrphans[:i], prevOrphans[i+1:]...)
return
}
}
}
删除孤块的过程中,同时删除父块
孤块处理逻辑
protocol/block.go
func (c *Chain) processBlock(block *types.Block) (bool, error) {
blockHash := block.Hash()
if c.BlockExist(&blockHash) {
log.WithFields(log.Fields{"hash": blockHash.String(), "height": block.Height}).Info("block has been processed")
return c.orphanManage.BlockExist(&blockHash), nil
}
if parent := c.index.GetNode(&block.PreviousBlockHash); parent == nil {
c.orphanManage.Add(block)
return true, nil
}
if err := c.saveBlock(block); err != nil {
return false, err
}
bestBlock := c.saveSubBlock(block)
// ...
}
processBlock函数处理block块加入区块链上之前的过程。
c.BlockExist判断当前block块是否存在于区块链上或是否存在孤块缓存池中,如果存在则返回。
c.index.GetNode判断block块的父节点是否存在。如果在现有的主链中却未找到它的父区块则将block块添加到孤块缓存池。
c.saveBlock走到了这一步说明,block父节点是存在于区块链,则将block块存储到区块链。该函数会验证区块和交易有效性。
saveSubBlock 代码如下:
func (c *Chain) saveSubBlock(block *types.Block) *types.Block {
blockHash := block.Hash()
prevOrphans, ok := c.orphanManage.GetPrevOrphans(&blockHash)
if !ok {
return block
}
bestBlock := block
for _, prevOrphan := range prevOrphans {
orphanBlock, ok := c.orphanManage.Get(prevOrphan)
if !ok {
log.WithFields(log.Fields{"hash": prevOrphan.String()}).Warning("saveSubBlock fail to get block from orphanManage")
continue
}
if err := c.saveBlock(orphanBlock); err != nil {
log.WithFields(log.Fields{"hash": prevOrphan.String(), "height": orphanBlock.Height}).Warning("saveSubBlock fail to save block")
continue
}
if subBestBlock := c.saveSubBlock(orphanBlock); subBestBlock.Height > bestBlock.Height {
bestBlock = subBestBlock
}
}
return bestBlock
}
saveSubBlock 在孤块缓存池中查询是否存在当前区块的下一个区块。比如当前区块高度为100,则在孤块缓存池中查询是否有区块高度为101的区块。如果存在则将101区块存储到区块链并从孤块缓存池中删除该区块。
saveSubBlock是一个递归函数的实现。目的是为了寻找最深叶子节点的递归方式。比如当前区块高度为100的,递归查询出高度为99、98、97等高度的区块。
Derek解读Bytom源码-孤块管理的更多相关文章
- Derek解读Bytom源码-持久化存储LevelDB
作者:Derek 简介 Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchain/bytom ...
- Derek解读Bytom源码-创世区块
作者:Derek 简介 Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchain/bytom ...
- Derek解读Bytom源码-Api Server接口服务
作者:Derek 简介 Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchain/bytom ...
- Derek解读Bytom源码-启动与停止
作者:Derek 简介 Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchain/bytom ...
- Derek解读Bytom源码-protobuf生成比原核心代码
作者:Derek 简介 Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchain/bytom ...
- Derek解读Bytom源码-P2P网络 upnp端口映射
作者:Derek 简介 Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchain/bytom ...
- Derek解读Bytom源码-P2P网络 地址簿
作者:Derek 简介 Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchain/bytom ...
- 入口开始,解读Vue源码(一)-- 造物创世
Why? 网上现有的Vue源码解析文章一搜一大批,但是为什么我还要去做这样的事情呢?因为觉得纸上得来终觉浅,绝知此事要躬行. 然后平时的项目也主要是Vue,在使用Vue的过程中,也对其一些约定产生了一 ...
- 鸿蒙OS的系统调用是如何实现的? | 解读鸿蒙源码
本文将首先带您回顾"系统调用"的概念以及它的作用,然后从经典的Hello World开始,逐行代码层层分析--鸿蒙OS的系统调用是如何实现的. 写在前面 9月10号 华为开发者大会 ...
随机推荐
- Python读取excel数据类型处理
一.python xlrd读取datetime类型数据:https://blog.csdn.net/y1535766478/article/details/78128574 (1)使用xlrd读取出来 ...
- 输入一串字符,检查是否可以组成friend
"""输入一串字符,检查是否可以组成friend""" from collections import Counter def foo(nu ...
- JAVA程序测试感受
上周四下午,我们进行了JAVA测试,心里很慌,在家中只是学习了JAVA程序的输入.输出以及各种数据类型使用而已,王建民老师给我们发了一份JAVA的课前测试样卷,是关于学生信息管理系统的,我们提前从学长 ...
- JDK8 元空间
1. 运行时常量池和静态变量都存储到了堆中,MetaSpace存储类的元数据,MetaSpace直接申请在本地内存中(Native memory),这样类的元数据分配只受本地内存大小的限制,OOM问题 ...
- Linux服务器---邮件服务openwebmail配置
配置openwebmail 通过修改配置文件openwebmail.conf,实现个性化邮箱定制.用户可以自由的更改邮箱logo.域名.容量 .签名等信息. 1.重定向index,快速打开邮箱.将下面 ...
- POJ 1182 食物链 (种类并查集)
动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形.A吃B, B吃C,C吃A. 现有N个动物,以1-N编号.每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种.有人用两种说 ...
- 前端框架VUE----组件的创建
vue的核心基础就是组件的使用,玩好了组件才能将前面学的基础更好的运用起来.组件的使用更使我们的项目解耦合.更加符合vue的设计思想MVVM. 那接下来就跟我看一下如何在一个Vue实例中使用组件吧! ...
- 理解Sql Server 事务隔离层级(Transaction Isolation Level)
关于Sql Server 事务隔离级别,百度百科是这样描述的 隔离级别:一个事务必须与由其他事务进行的资源或数据更改相隔离的程度.隔离级别从允许的并发副作用(例如,脏读或虚拟读取)的角度进行描述. 隔 ...
- c#测试执行时间的方法
获取当前实例测量出来的总的运行时间 Stopwatch sp = new Stopwatch(); sp.Start(); //要测试的代码块 sp.Stop(); Console.WriteLine ...
- C++形参中const char * 与 char * 的区别
在函数调用时,我们经常看见一个函数的接受参数为(const char *); 例如strlen()函数,它的定义为: size_t strlen( const char *str); 那么将形参设置为 ...