长安链源码学习--区块验证(BlockVerify)(六)

作者:明神特烦恼
公众号:明神特烦恼

当节点接收到其他节点发送的提案信息 或者 接收到其他节点同步过来的区块,需要对区块进行验证,确保区块的有效性。

待验证区块的数据结构:

type Block struct {
    // header of the block
    Header *BlockHeader `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"`
    // execution sequence of intra block transactions is generated by proposer
    Dag *DAG           `protobuf:"bytes,2,opt,name=dag,proto3" json:"dag,omitempty"`
    Txs []*Transaction `protobuf:"bytes,3,rep,name=txs,proto3" json:"txs,omitempty"`
    // stores the voting information of the current block
    // not included in block hash value calculation
    AdditionalData *AdditionalData `protobuf:"bytes,4,opt,name=additional_data,json=additionalData,proto3" json:"additional_data,omitempty"`
}

区块验证内容如下:
1)区块中交易总数 不能超过 配置的区块最大交易数限制
2)区块体的交易总数与Header.TxCount相同
3)本节点的区块高度 >= 验证区块的高度,且能否与前一区块的Hash一致
4)提案人身份认证有效、权限检测通过(权限管理在后面章节单独介绍)
5)区块内部txid无重复
6)通过DAG生成读写集,每笔交易的读写集Hash与tx.Result.RwSetHash一致
7)每笔交易执行结果与区块内tx.Result一致

其中第6步与第7步都为多线程并发验证,这里体现出DAG的作用。

type DAG struct {
    // sequence number of transaction topological sort
    //the sequence number of the transaction topological sort associated with the transaction
    Vertexes []*DAG_Neighbor `protobuf:"bytes,2,rep,name=vertexes,proto3" json:"vertexes,omitempty"`
}

之前的章节,我们分析过DAG的数据结构,Vertexes的每个索引表示具体的交易编号。这里处理的思路是,如果某个节点与其他节点不是邻居,代表没有关联交易,可以并发生成读写集。这样可以加快读写集的生成。具体逻辑在SimulateWithDag函数中。

func (ts *TxSchedulerImpl) SimulateWithDag(block *commonpb.Block, snapshot protocol.Snapshot) (map[string]*commonpb.TxRWSet, map[string]*commonpb.Result, error) {
    txMapping := make(map[int]*commonpb.Transaction)
    for index, tx := range block.Txs {
        txMapping[index] = tx
    }

    // 交易间关联关系转化,记录每笔交易与哪些交易有关联关系,用true表示
    dag := block.Dag
    dagRemain := make(map[int]dagNeighbors)
    for txIndex, neighbors := range dag.Vertexes {
        dn := make(dagNeighbors)
        for _, neighbor := range neighbors.Neighbors {
            dn[int(neighbor)] = true
        }
        dagRemain[txIndex] = dn
    }


    txBatchSize := len(block.Dag.Vertexes)
    runningTxC := make(chan int, txBatchSize)
    doneTxC := make(chan int, txBatchSize)

    timeoutC := time.After(ScheduleWithDagTimeout * time.Second)
    finishC := make(chan bool)
    var goRoutinePool *ants.Pool
    var err error
    poolCapacity := runtime.NumCPU() * 4
    if goRoutinePool, err = ants.NewPool(poolCapacity, ants.WithPreAlloc(true)); err != nil {
        return nil, nil, err
    }
    defer goRoutinePool.Release()
 // 启动校验线程池,准备并发生成读写集
    go func() {
        for {
            select {
            case txIndex := <-runningTxC:
                tx := txMapping[txIndex]
                err := goRoutinePool.Submit(func() {
                    ts.log.Debugf("run vm with dag for tx id %s", tx.Header.GetTxId())
                    txSimContext := newTxSimContext(ts.VmManager, snapshot, tx)
                    runVmSuccess := true
                    var txResult *commonpb.Result
                    var err error
                    //调用合约模拟交易执行
                    if txResult, err = ts.runVM(tx, txSimContext); err != nil {
                        runVmSuccess = false
                        txSimContext.SetTxResult(txResult)
                        ts.log.Errorf("failed to run vm for tx id:%s during simulate with dag, tx result:%+v, error:%+v", tx.Header.GetTxId(), txResult, err)
                    } else {
                        //ts.log.Debugf("success to run vm for tx id:%s during simulate with dag, tx result:%+v", tx.Header.GetTxId(), txResult)
                        txSimContext.SetTxResult(txResult)
                    }

                    applyResult, applySize := snapshot.ApplyTxSimContext(txSimContext, runVmSuccess)
                    if !applyResult {
                        ts.log.Debugf("failed to apply according to dag with tx %s ", tx.Header.TxId)
                        runningTxC <- txIndex
                    } else {
                        ts.log.Debugf("apply to snapshot tx id:%s, result:%+v, apply count:%d", tx.Header.GetTxId(), txResult, applySize)
                        doneTxC <- txIndex //交易执行成功
                    }
                    // If all transactions in current batch have been successfully added to dag
                    if applySize >= txBatchSize {
                        finishC <- true
                    }
                })
                if err != nil {
                    ts.log.Warnf("failed to submit tx id %s during simulate with dag, %+v", tx.Header.GetTxId(), err)
                }
            case doneTxIndex := <-doneTxC:
                ts.shrinkDag(doneTxIndex, dagRemain) //踢掉依赖关系中第一层节点,那么依赖````doneTxIndex````的交易变成没有依赖的交易
                //寻找没有依赖其他交易的交易
                txIndexBatch := ts.popNextTxBatchFromDag(dagRemain) 
                //ts.log.Debugf("pop next tx index batch %v", txIndexBatch)
                for _, tx := range txIndexBatch {
                    runningTxC <- tx
                }
            case <-finishC:
                ts.log.Debugf("schedule with dag finish")
                ts.scheduleFinishC <- true
                return
            case <-timeoutC:
                ts.log.Errorf("schedule with dag timeout")
                ts.scheduleFinishC <- true
                return
            }
        }
    }()

    //寻找没有依赖其他交易的交易
    txIndexBatch := ts.popNextTxBatchFromDag(dagRemain)

    go func() {
        for _, tx := range txIndexBatch {
            //没有依赖其他交易的交易可以并发生成读写集,而不会造成冲突
            runningTxC <- tx
        }
    }()

    <-ts.scheduleFinishC //等待检查完毕
    snapshot.Seal()

    ts.log.Infof("simulate with dag end, size %d, time used %+v", len(block.Txs), time.Since(startTime))

    // Return the read and write set after the scheduled execution

    for _, txRWSet := range snapshot.GetTxRWSetTable() {
        if txRWSet != nil {
            txRWSetMap[txRWSet.TxId] = txRWSet
        }
    }
    return txRWSetMap, snapshot.GetTxResultMap(), nil
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容