专栏首页比原链Derek解读Bytom源码-孤块管理

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等高度的区块。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Derek解读Bytom源码-孤块管理

    Gitee地址:https://gitee.com/BytomBlockchain/bytom

    比原链Bytom
  • 比原链CTO James | Go语言成为区块链主流开发语言的四点理由

    11月24日,比原链CTO James参加了Go中国举办的Gopher Meetup杭州站活动,与来自阿里、网易的技术专家带来Kubernetes、区块链、日志...

    比原链Bytom
  • 人人都应学习的公链知识——比原总体架构

    PPT链接:introduction-bytom-architecture-2018

    比原链Bytom
  • Derek解读Bytom源码-孤块管理

    Gitee地址:https://gitee.com/BytomBlockchain/bytom

    比原链Bytom
  • 投资区块链别只知道“挖矿”,这里还有5个值得投资的领域

    【数据猿导读】 ICO是Initial CoinOffering缩写,意为首次币发行,源自股票市场的首次公开发行(IPO)概念,区块链公司通过提供代币或虚拟货币...

    数据猿
  • Ionic3学习笔记(十)实现夜间模式功能

    在 ./src/theme 文件夹下创建 theme.light.scss、theme.dark.scss 2个文件,分别用于日间模式、夜间模式的设置。

    Theo Tsao
  • # iOS Block的本质(三)

    struct __main_block_impl_0 { struct __block_impl impl; struct __main_blo...

    用户1941540
  • WCF技术剖析_学习笔记之三

    数据契约 通过特性来定义,保证服务端和客户端对数据有一致性的理解。 [DataContract]用于枚举、类、结构体。而不用于接口。不可被继承。 3个属性成员:...

    小端
  • macOS/iOS 代理 V2Ray 小白级配置

    GitHub: https://github.com/V2Ray/v2ray-core 官方网站:https://www.v2ray.com/

    iOSDevLog
  • spring-cloud-sleuth+zipkin源码探究

      粗略看了下spring cloud sleuth core源码,发现内容真的有点多,它支持了很多类型的链路追踪,我就找其中一个比较有代表性的深入剖析下源码结...

    老梁

扫码关注云+社区

领取腾讯云代金券