源码

初链主网上线技术解读之-混合共识

背景

从2017年11月启动至今,经过历时近一年的研究、开发与测试,初链主网Beta版于新加坡时间2018年09月28日08:00正式上线,在此之前,07:56分PBFT委员会第一次共识出块和TrueChain fPOW创世区块被挖出,为了让更多人从技术上去理解初链,初链社区发布了初链技术解读的任务,我也借这次任务开始我第一篇博客。

说明

初链本次上线体现了五大亮点,包括混合共识,FPow公链,TrueHash抗ASIC的挖矿算法,PBFT委员会的随机选举机制,高TPS。本文主要针对初链的混合共识进行解读。后续一一解读其它亮点。

名称解释

共识: 共识顾名思义,共同的认识,在区块链世界中早期的共识算法代表要算比特币的pow。pow简单来讲,就是前一个区块有一个随机数,大家都去猜,谁先猜出来谁就有记账权,记账好了后,就将区块广播给其它节点,如果要想从源码层解读,请参考文档“PoW挖矿算法原理及其在比特币、以太坊中的实现”。

混合共识: pow共识安全但不高效,转账效率低,为了解决这个问题,后面出现了pos dpos,虽然效率提高了但是已不再那么去中心化。为了找到效率和安全的平衡点,混合共识出现,一种共识解决记账,一种共识解决区中心化。本文主要解读初链,让我们来看看true链是怎么找到这个平衡点的。

技术架构

初链采用双链结构,如图所示。一条快链,一条慢链。快链是交易块,里面记录的是很多交易。慢链是水果块,里面记录的是很多水果,水果一次递增,每个水果映射一个交易块。

混合共识又是怎么一回事呢?

在初链中,采用改进拜赞庭(fbft)和工作量证明(pow)两种共识。fbft主要解决交易效率问题,如图所示,委员会由41个节点组成,相对于比特比节点而已,已经少之又少,当交易网络的交易提交到委员会网络以后,交易能够得到快速的确认,自然整个网络的交易效率也就提高了额。pow主要解决去中心化问题,每个水果和每个快区块一一对应,而每个水果又会被pow再次打包出块,如果想要篡改交易,首先要篡改pow里面的水果,就必须控制51%的算力。

这有点虚吧!没问题,下面就来点实际的。

从启动到运行bft有很深的调用链,如下可以找到node的NewNode方法

从启动到运行bft有很深的调用链,如下可以找到node的NewNode方法
main-->>cmd:StartNode()
cmd-->>node:Start()
node-->>service:start()
service-->>backend-Truechain:Start()
truechain-->>pbft_agent:start()loop()
pbft_agent-->>commitee:PutNodes()
commitee-->>pbftserver:PutNodes()
pbftserver-->>proxy_server:NewServer()
proxy_server-->>node:NewNode()

有关pbft算法过程详情可以参考【链接5】,大概有5个过程1. Request 2. Pre-Prepare 3. Prepare 4. Commit 5.Reply。true链进行了改造。整个逻辑如图,重点是node的resolveMsg,该方法是true链委员会和fbft_impl起到核心关联作用。

相关代码如下

type PBFT interface {
    StartConsensus(request *RequestMsg) (*PrePrepareMsg, error)
    PrePrepare(prePrepareMsg *PrePrepareMsg) (*VoteMsg, error)
    Prepare(prepareMsg *VoteMsg) (*VoteMsg, error)
    Commit(commitMsg *VoteMsg) (*ReplyMsg, *RequestMsg, error)
}

实现类在pbft_impl.go中,这里只展示一段,其它方法感兴趣的同学可以自行查看
func (state *State) PrePrepare(prePrepareMsg *PrePrepareMsg) (*VoteMsg, error) {
    // Get ReqMsgs and save it to its logs like the primary.
    state.MsgLogs.ReqMsg = prePrepareMsg.RequestMsg

    // Verify if v, n(a.k.a. sequenceID), d are correct.
    if !state.verifyMsg(prePrepareMsg.ViewID, prePrepareMsg.SequenceID, prePrepareMsg.Digest) {
        return nil, errors.New("pre-prepare message is corrupted")
    }
    // Change the stage to pre-prepared.
    state.CurrentStage = PrePrepared

    return &VoteMsg{
        ViewID:     state.ViewID,
        SequenceID: prePrepareMsg.SequenceID,
        Digest:     prePrepareMsg.Digest,
        MsgType:    PrepareMsg,
        Height:     prePrepareMsg.Height,
    }, nil
}
而上文提到的node.NewNode中有这么一段,其中resolveMsg就是处理各种fbft过程传统msg,对msg校验,然后发起下一阶段的请求。
// Start message dispatcher
    go node.dispatchMsg()

    // Start alarm trigger
    go node.alarmToDispatcher()

    // Start message resolver
    go node.resolveMsg()

    //start backward message dispatcher
    go node.dispatchMsgBackward()

    //start Process message commit wait
    go node.processCommitWaitMessage()

resolveMsg代码如下
func (node *Node) resolveMsg() {
    for {
        // Get buffered messages from the dispatcher.
        msgs := <-node.MsgDelivery
        switch msgs.(type) {
        case []*consensus.RequestMsg:
            errs := node.resolveRequestMsg(msgs.([]*consensus.RequestMsg))
            if len(errs) != 0 {
                for _, err := range errs {
                    fmt.Println(err)
                }
                // TODO: send err to ErrorChannel
            }
        case []*consensus.PrePrepareMsg:
            errs := node.resolvePrePrepareMsg(msgs.([]*consensus.PrePrepareMsg))
            if len(errs) != 0 {
                for _, err := range errs {
                    fmt.Println(err)
                }
                // TODO: send err to ErrorChannel
            }
        case []*consensus.VoteMsg:
            voteMsgs := msgs.([]*consensus.VoteMsg)
            if len(voteMsgs) == 0 {
                break
            }

            if voteMsgs[0].MsgType == consensus.PrepareMsg {
                errs := node.resolvePrepareMsg(voteMsgs)
                if len(errs) != 0 {
                    for _, err := range errs {
                        fmt.Println(err)
                    }
                    // TODO: send err to ErrorChannel
                }
            } else if voteMsgs[0].MsgType == consensus.CommitMsg {
                errs := node.resolveCommitMsg(voteMsgs)
                if len(errs) != 0 {
                    for _, err := range errs {
                        fmt.Println(err)
                    }
                    // TODO: send err to ErrorChannel
                }
            }
        }
    }
}

还有一个重要的问题必须要回答,因为现在fbft调用链找到了额,fbft过程消息处理机制也找到了额,但是共识的是什么?这个问题一直没回答。其实fbft共识的是leader和fastblock。代码如下,中间省去部分细节。

app启动是会开启维护员leader选举
pbft_agent.go
    case types.CommitteeStart:
                log.Info("CommitteeStart...")
                self.committeeMu.Lock()
                self.setCommitteeInfo(self.NextCommitteeInfo, CurrentCommittee)
                self.committeeMu.Unlock()
                if self.IsCommitteeMember(self.CommitteeInfo) {
                    go self.server.Notify(self.CommitteeInfo.Id, int(ch.Option))//发送选举通知  Notify为调用pbftserver.work
                }
pbftserver.go work代码如下
func (ss *PbftServerMgr) work(cid *big.Int, acChan <-chan *consensus.ActionIn) {
    for {
        select {
        case ac := <-acChan:
            if ac.AC == consensus.ActionFecth {
                req, err := ss.GetRequest(cid)
                if err == nil && req != nil {
                    if server, ok := ss.servers[cid.Uint64()]; ok {
                        server.Height = big.NewInt(req.Height)
                        server.server.PutRequest(req) //发起共识
                    } else {
                        fmt.Println(err.Error())
                    }
                } else {
                    lock.PSLog(err.Error())
                }
            } else if ac.AC == consensus.ActionBroadcast {
                ss.Broadcast(ac.Height)
            } else if ac.AC == consensus.ActionFinish {
                return
            }
        }
    }

选举完成之后将自己设置为leader
func (ss *PbftServerMgr) PutCommittee(committeeInfo *types.CommitteeInfo) error {
    lock.PSLog("PutCommittee", committeeInfo.Id, committeeInfo.Members)
    id := committeeInfo.Id
    members := committeeInfo.Members
    if id == nil || len(members) <= 0 {
        return errors.New("wrong params...")
    }
    if _, ok := ss.servers[id.Uint64()]; ok {
        return errors.New("repeat ID:" + id.String())
    }
    leader := members[0].Publickey
    infos := make([]*types.CommitteeNode, 0)
    server := serverInfo{   //第一次共识完成,选出leader
        leader: leader,
        nodeid: common.ToHex(crypto.FromECDSAPub(ss.pk)),
        info:   infos,
        Height: new(big.Int).Set(common.Big0),
        clear:  false,
    }
    for _, v := range members {
        server.insertMember(v)
    }
    ss.servers[id.Uint64()] = &server
    return nil
}
在fb出块的时候有一个leader判断动作
func (ss *PbftServerMgr) GetRequest(id *big.Int) (*consensus.RequestMsg, error) {
    // get new fastblock 产出一个fastblock
    server, ok := ss.servers[id.Uint64()]
    if !ok {
        return nil, errors.New("wrong conmmitt ID:" + id.String())
    }
    // the node must be leader
    if !bytes.Equal(crypto.FromECDSAPub(server.leader), crypto.FromECDSAPub(ss.pk)) { //leader判断
        return nil, errors.New("local node must be leader...")
    }
    lock.PSLog("AGENT""FetchFastBlock""start")
    fb, err := ss.Agent.FetchFastBlock()
    lock.PSLog("AGENT""FetchFastBlock", err == nil"end")
    if err != nil {
        return nil, err
    }

    if fb := ss.getBlock(fb.NumberU64()); fb != nil {
        return nil, errors.New("same height:" + fb.Number().String())
    }

    fmt.Println(len(ss.blocks))
    sum := ss.getBlockLen()

    if sum > 0 {
        last := ss.getLastBlock()
        if last != nil {
            cur := last.Number()
            cur.Add(cur, common.Big1)
            if cur.Cmp(fb.Number()) != 0 {
                return nil, errors.New("wrong fastblock,lastheight:" + cur.String() + " cur:" + fb.Number().String())
            }
        }
    }

    ss.putBlock(fb.NumberU64(), fb)
    data, err := rlp.EncodeToBytes(fb)
    if err != nil {
        return nil, err
    }
    msg := hex.EncodeToString(data)
    val := &consensus.RequestMsg{ //返回一个fastblock共识消息
        ClientID:  server.nodeid,
        Timestamp: time.Now().Unix(),
        Operation: msg,
        Height:    fb.Number().Int64(),
    }
    return val, nil
}

本来想对pow在解析一下,发现有童鞋写的非常好了额,感兴趣的可以移步链接【6】

运行状况

再来看看true链运行情况。

总体运行情况如下图:图中可以看出目前委员会为6个,FB69758个,SB982个。委员会6据了解是前期为了主网稳定,但6个节点那么允许作恶容忍6*0.25=1.2个,作恶风险还是比较高,如果出现2台机器作恶,将会产生混乱,还是希望官网引起重视。

再来看混合共识的结果

快链:初略看了一下大部分block的共识委员会都是6个,说明目前没有分岔,但有一个疑问,为什么第一个的block高度不是1?

慢链:初略看了一下大部分挖矿地址比较分散,说明pow效果还是很明显真正做到了去中心化。但是大部分区块没有交易数,很多空块会占用大量的存储空间,而且毫无意义,因为没有交易。我想一种采用一种压缩技术,对连续空块进行压缩;一种方式降低出块速率让速率和交易量挂钩。这两种思路都可以解决空块暂用磁盘问题,我的一点拙见。

Snail Blocks:进入SnailBlocks发现没有水果,不知是我理解的问题,还是浏览器的bug,没有发现水果。按照逻辑应该水果能被查询出来才算正常,水果作为Fast Block再次打包的凭证,如果没有水果很难说pow发挥作用,希望是浏览器的bug。

结论

节点启动以后会先发起一轮fbft的共识选举,选举leader有记账权,记账的表现形式为fastblock。如果想研究pow机制请参考链接【6】。这两种共识一种保障效率,一种保障安全,在去中心化和效率选进行了一个折中的选择。从上线的运行效果来看,两种共识能够完美配合,但是仍存在一些不足,比如空块暂用磁盘可以进行优化,水果在区块链浏览器中无法查看,委员会只有6个安全有待进一步提升。最后,初链做出的努力和结果可喜可贺,为投资者和社区交了一份满意的答卷。

参考资料

  1. https://www.8btc.com/article/106800论比特币系统的共识规则

  2. http://blog.51cto.com/11821908/2059711PoW挖矿算法原理及其在比特币、以太坊中的实现

  3. http://www.sohu.com/a/239677141_100092199POW+POS混合共识机制有多牛?区块链大佬揭秘!

  4. https://blog.csdn.net/qq_22269733/article/details/83025225Truechain主网Beta版交易流程解析

  5. https://blog.csdn.net/jerry81333/article/details/74303194/区块链共识算法 PBFT(拜占庭容错)、PAXOS、RAFT简述

  6. https://blog.csdn.net/sinat_27935057/article/details/83193018初链主网Beta版慢链挖矿解析

作者:tianjueshao 

原文:https://blog.csdn.net/tianjueshao/article/details/83154451 

(0)

本文由 投稿者 创作,文章地址:https://blog.isoyu.com/archives/chulianzhuwangshangxianjishujieduzhi-hunhegongshi.html
采用知识共享署名4.0 国际许可协议进行许可。除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。最后编辑时间为:10 月 26, 2018 at 12:58 上午

热评文章

发表回复

[必填]

我是人?

提交后请等待三秒以免造成未提交成功和重复