转自: https://blog.csdn.net/HiBlock/article/details/80212499

个人感觉对交易流程描述的比较清楚,转载以备查看。

1

典型交易流程

下图所示为Hyperledger Fabric 1.0典型的交易流程图。

从上一节的网络节点架构中,我们已经了解到基于Hyperledger Fabric 1.0的区块链应用中涉及几个节点角色:应用程序、背书节点、排序服务节点和主节点。在图3-4中,假定各节点已经提前颁发好证书,且已正常启动,并加入已经创建好的通道。后面的步骤介绍在已经实例化了的链码通道上从发起一个调用交易到最终记账的全过程。

1、创建交易提案并发送给背书节点

使用应用程序构造交易提案,SignedProposal的结构如下所示:

SignedProposal: {

    ProposalBytes(Proposal): {

        Header: {

            ChannelHeader: {

                Type: "HeaderType_ENDORSER_TRANSACTION",

                TxId: TxId,

                Timestamp: Timestamp,

                ChannelId: ChannelId,

                Extension(ChaincodeHeaderExtension): {

                    PayloadVisibility: PayloadVisibility,

                    ChaincodeId: {

                        Path: Path,

                        Name: Name,

                        Version: Version

                    }

                },

                Epoch: Epoch

            },

            SignatureHeader: {

                Creator: Creator,

                Nonce: Nonce

            }

        },

        Payload: {

            ChaincodeProposalPayload: {

                Input(ChaincodeInvocationSpec): {

                    ChaincodeSpec: {

                        Type: Type,

                        ChaincodeId: {

                            Name: Name

                        },

                        Input(ChaincodeInput): {

                            Args: []

                        }

                    }

                },

                TransientMap: TransientMap

            }

        }

    },

    Signature: Signature

}

我们来看看上面的结构,SignedProposal是封装了Proposal的结构,添加了调用者的签名信息。背书节点会根据签名信息验证其是否是一个有效的消息。Proposal由两个部分组成:消息头和消息结构。消息结构详细的解释参考后面的章节。这里简单讲一下消息头。

消息头(Header)也包含两项内容。

通道头(ChannelHeader):通道头包含了与通道和链码调用相关的信息,比如在哪个通道上调用哪个版本的链码。TxId是应用程序本地生成的交易号,跟调用者的身份证书相关,可以避免交易号的冲突,背书节点和记账节点都会校验是否存在重复交易。

签名头(SignatureHeader):签名头包含了调用者的身份证书和一个随机数,用于消息的有效性校验。

应用程序构造好交易提案请求后,选择背书节点执行并进行背书签名。背书节点是链码背书策略里指定的节点。有一些背书节点是离线的,其他的背书节点可以拒绝对交易进行背书,也可以不背书。应用程序可以尝试使用其他可用的背书节点来满足策略。应用程序以何种顺序给背书节点发送背书请求是没有关系的,正常情况下背书节点执行后的结果是一致的,只有背书节点对结果的签名不一样。

2、背书节点模拟交易并生成背书签名

背书节点在收到交易提案后会进行一些验证,包括:

  • 交易提案的格式是否正确;

  • 交易是否提交过(重复攻击保护);

  • 交易签名有效(通过 MSP);

  • 交易提案的提交者在当前通道上是否已授权有写权限。

验证通过后,背书节点会根据当前账本数据模拟执行链码中的业务逻辑并生成读写集(RwSet),其中包含响应值、读写集等。在模拟执行时账本数据不会更新。而后背书节点对这些读写集进行签名成为提案响应(Proposal Response),然后返回给应用程序。

ProposalResponse的结构如下:

ProposalResponse: {

    Version: Version,

    Timestamp: Timestamp,

    Response: {

        Status: Status,

        Message: Message,

        Payload: Payload

    },

    Payload(ProposalResponsePayload): {

        ProposalHash: ProposalHash,

        Extension(ChaincodeAction): {

            Results(TxRwSet): {

                NsRwSets(NsRwSet): [

                    NameSpace: NameSpace,

                    KvRwSet: {

                        Reads(KVRead): [

                            Key: Key,

                            Version: {

                                BlockNum: BlockNum,

                                TxNum: TxNum

                            }

                        ],

                        RangeQueriesInfo(RangeQueryInfo): [

                            StartKey: StartKey,

                            EndKey: EndKey,

                            ItrExhausted: ItrExhausted,

                            ReadsInfo: ReadsInfo

                        ],

                        Writes(KVWrite): [

                            Key: Key,

                            IsDelete: IsDelete,

                            Value: Value

                        ]

                    }

                ]

            },

            Events(ChaincodeEvent): {

                ChaincodeId: ChaincodeId,

                TxId: TxId,

                EventName: EventName,

                Payload: Payload

            }

            Response: {

                Status: Status,

                Message: Message,

                Payload: Payload

            },

            ChaincodeId: ChaincodeId

        }

    },

    Endorsement: {

        Endorser: Endorser,

        Signature: Signature

    }

}

返回的ProposalResponse中包含了读写集、背书节点签名以及通道名称等信息。

3、收集交易的背书

应用程序收到ProposalResponse后会对背书节点签名进行验证,所有节点接收到任何消息后都是需要先验证消息合法性的。如果链码只进行账本查询,应用程序会检查查询响应,但不会将交易提交给排序服务节点。如果链码对账本进行Invoke操作,则须提交交易给排序服务进行账本更新,应用程序会在提交交易前判断背书策略是否满足。如果应用程序没有收集到足够的背书就提交交易了,记账节点在提交验证阶段会发现交易不能满足背书策略,标记为无效交易。

如何选择背书节点呢?目前fabric-sdk-go默认的实现是把配置文件选项channels.mychannel.peers(其中的mychannel需要替换成实际的通道名称)里的节点全部添加为背书节点,需要等待所有背书节点的背书签名。应用程序等待每个背书节点执行的超时时间是通过配置文件选项client.peer.timeout.connection设置的,配置文件的示例给出的是3秒,根据实际情况调整,如果没有设置就是5秒的默认值。

4、构造交易请求并发送给排序服务节点

应用程序接收到所有的背书节点签名后,根据背书签名调用SDK生成交易,广播给排序服务节点。生成交易的过程比较简单,确认所有的背书节点的执行结果完全一致,再将交易提案、提案响应和背书签名打包生成交易。交易的结构如下:

Envelope: {

    Payload: {

        Header: {

            ChannelHeader: {

                Type: "HeaderType_ENDORSER_TRANSACTION",

                TxId: TxId,

                Timestamp: Timestamp,

                ChannelId: ChannelId,

                Extension(ChaincodeHeaderExtension): {

                    PayloadVisibility: PayloadVisibility,

                    ChaincodeId: {

                        Path: Path,

                        Name: Name,

                        Version: Version

                    }

                },

                Epoch: Epoch

            },

            SignatureHeader: {

                Creator: Creator,

                Nonce: Nonce

            }

        },

        Data(Transaction): {

            TransactionAction: [

                Header(SignatureHeader): {

                    Creator: Creator,

                    Nonce: Nonce

                },

                Payload(ChaincodeActionPayload): {

                    ChaincodeProposalPayload: {

                        Input(ChaincodeInvocationSpec): {

                            ChaincodeSpec: {

                                Type: Type,

                                ChaincodeId: {

                                    Name: Name

                                },

                                Input(ChaincodeInput): {

                                    Args: []

                                }

                            }

                        },

                        TransientMap: nil

                    },

                    Action(ChaincodeEndorsedAction): {

                        Payload(ProposalResponsePayload): {

                            ProposalHash: ProposalHash,

                            Extension(ChaincodeAction): {

                                Results(TxRwSet): {

                                    NsRwSets(NsRwSet): [

                                        NameSpace: NameSpace,

                                        KvRwSet: {

                                            Reads(KVRead): [

                                                Key: Key,

                                                Version: {

                                                    BlockNum: BlockNum,

                                                    TxNum: TxNum

                                                }

                                            ],

                                            RangeQueriesInfo(RangeQueryInfo): [

                                                StartKey: StartKey,

                                                EndKey: EndKey,

                                                ItrExhausted: ItrExhausted,

                                                ReadsInfo: ReadsInfo

                                            ],

                                            Writes(KVWrite): [

                                                Key: Key,

                                                IsDelete: IsDelete,

                                                Value: Value

                                            ]

                                        }

                                    ]

                                },

                                Events(ChaincodeEvent): {

                                    ChaincodeId: ChaincodeId,

                                    TxId: TxId,

                                    EventName: EventName,

                                    Payload: Payload

                                }

                                Response: {

                                    Status: Status,

                                    Message: Message,

                                    Payload: Payload

                                },

                                ChaincodeId: ChaincodeId

                            }

                        },

                        Endorsement: [

                            Endorser: Endorser,

                            Signature: Signature

                        ]

                    }

                }

            ]

        }

    },

    Signature: Signature

}

我们来看交易信封的几个对应关系:

  • Envelope.Payload.Header同交易提案SignedProposal.Proposal.Header;

  • Envelope.Payload.Data.TransactionAction.Header是交易提案的提交者的身份信息,同SignedProposal.Proposal.Header.SignatureHeader和Envelope.Payload.Header.SignatureHeader是冗余的;

  • Envelope.Payload.Data.TransactionAction.Payload.ChaincodeProposalPayload同交易提案的SignedProposal.Proposal.Payload.ChaincodeProposalPayload,唯一不同的是,TransientMap强制设置为nil,目的是避免在区块中出现一些敏感信息;

  • Envelope.Payload.Data.TransactionAction.Payload.Action.Payload结构,其实和Proposal

  • Response.Payload结构完全一样;

  • Envelope.Payload.Data.TransactionAction.Payload.Action.Endorsement变成了数组,代表多个背书节点的背书签名。

整个信封Envelope的Signature是交易提交者对整个Envelope.Payload的签名。应用程序可以把生成的交易信封内容发送给任意选择的几个排序服务节点。

5、排序服务节点以对交易进行排序并生成区块

排序服务不读取交易的内容,如果在生成交易信封内容的时候伪造了交易模拟执行的结果,排序服务节点也不会发现,但会在最终的交易验证阶段校验出来并标记为无效交易。排序服务要做得很简单,先是接收网络中所有通道发出的交易信息,读取交易信封的Envelope.Payload.Header.ChannelHeader.ChannelId以获取通道名称,按各个通道上交易的接收时间顺序对交易信息进行排序,生成区块。

6、排序服务节点以广播给组织的主节点

排序服务节点生成区块以后会广播给通道上不同组织的主节点。

7、记账节点验证区块内容并写入区块

背书节点是动态角色,只要参与交易的背书就是背书节点,哪些交易选择哪些节点作为背书节点是由应用程序选择的,这需要满足背书策略才能生效。所有的背书节点都属于记账节点。所有的Peer节点都是记账节点,记录的是节点已加入通道的账本数据。记账节点接收到的是排序服务节点生成的区块,验证区块交易的有效性,提交到本地账本后再产生一个生成区块的事件,监听区块事件的应用程序可以进行后续的处理。

如果接收到的区块是配置区块,则会更新缓存的配置信息。记账节点的处理流程如图所示。

交易数据的验证

区块数据的验证是以交易验证为单位的,每次对区块进行验证时都会生成一个交易号的位图TxValidationFlags,它记录每个交易号的交易验证状态,只有状态为TxValidationCode_VALID才是有效的。位图也会写入到区块的元数据BlockMetadataIndex_TRANSACTIONS_FILTER中。交易验证的时候会检查以下内容:

  • 是否为合法的交易:交易格式是否正确,是否有合法的签名,交易内容是否被篡改;

  • 记账节点是否加入了这个通道。

基本的验证通过以后会提交给VSCC进行背书策略的验证。

记账节点与VSCC

链码的交易是隔离的,每个交易的模拟执行结果读写集TxRwSet都包含了交易所属的链码。为了避免错误地更新链码交易数据,在交易提交给系统链码VSCC验证交易内容之前,还会对链码进行校验。下面这些交易都是非法的:

  • 链码的名称或者版本为空;

  • 交易消息头里的链码名称Envelope.Payload.Header.ChannelHeader.Extension.ChaincodeId. Name和交易数据里的链码名称Envelope.Payload.Data.TransactionAction.Payload.ChaincodeProposalPayload.Input.ChaincodeSpec.ChaincodeId.Name不一致;

  • 链码更新当前链码数据时,生成读写集的链码版本不是LSCC记录的最新版本;

  • 应用程序链码更新了LSCC(生命周期管理系统链码)的数据;

  • 应用程序链码更新了不可被外部调用的系统链码的数据;

  • 应用程序链码更新了不可被其他链码调用的系统链码的数据;

  • 调用了不可被外部调用的系统链码。

基于状态数据的验证和MVCC检查

交易通过VSCC检查以后,就进入记账流程。kvledger还会对读写集TxRwSet进行MVCC(Multi-Version Concurrency Control)检查。

kvledger实现的是基于键值对(key-value)的状态数据模型。对状态数据的键有3种操作:

  • 读状态数据;

  • 写状态数据;

  • 删除状态数据。

对状态数据的读操作有两种形式:

  • 基于单一键的读取;

  • 基于键范围的读取。

MVCC检查只对读数据进行校验,基本逻辑是对模拟执行时状态数据的版本和提交交易时状态数据的版本进行比较。如果数据版本发生变化或者某个键的范围数据发生变化,就说明这段时间之内有别的交易改变了状态数据,当前交易基于原有状态的处理就是有问题的。由于交易提交是并行的,所以在交易未打包生成区块之前,并不能确定最终的执行顺序。如果交易执行的顺序存在依赖,在MVCC检查的时候就会出现依赖的状态发生了变化,实际上是数据出现了冲突。图所示为基于状态的数据验证的流程图。

写集合本身包含了写和删除的数据,有一个状态位标识是否删除数据。为了提升效率,状态数据库的提交是批处理的,整个区块交易的状态数据同时提交,这也保证了整个区块的状态数据要么都提交成功,要么都提交失败。这时只会出现记录的账本数据和状态数据库不一致,不会出现区块的状态数据不一致的情况。当账本数据和状态数据库不一致时,可以通过状态数据库的检查点来标记。

无效交易的处理

伪造的交易会导致无效交易,正常的交易也可能出现无效交易。MVCC检查的是背书节点在模拟执行的时候,环境是否和记账节点提交交易时的环境一致,这里的环境是指状态数据库里数据的三元组(key、value、version)是否完全一致。如果正常提交的交易在这个过程中涉及的数据发生了变化,那么也会出现检查失败从而导致无效交易。在这种情况下,需要在上层的应用程序有一些补偿措施,比如调整交易打包的配置,重新提交失败的交易等。

在目前版本的实现中,无效交易也会保留在区块中,可以通过区块记录的元数据确定哪些是无效交易。无效交易的读写集不会提交到状态数据库中,不会导致状态数据库发生变化,只是会占用区块的大小,占用记账节点的硬盘空间。后续的版本会实现账本的精简,过滤掉无效交易。

8、在组织内部同步最新的区块

--------------------- 本文来自 HiBlock区块链社区 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/HiBlock/article/details/80212499?utm_source=copy

[转] 深度探索Hyperledger技术与应用之超级账本的典型交易流程的更多相关文章

  1. HyperLedger Fabric Introduction——区块链超级账本介绍

    介绍 HyperLedger Fabric是一个基于模块化架构的分布式账本解决方案平台,它拥有深度加密.便捷扩展.部署灵活及可插拔等特性.它设计之初的目的是支持不同组件的可插拔实现,并适应整个经济生态 ...

  2. HyperLedger Fabric 1.4 超级账本起源(5.1)

    至比特币开源以来,无数技术人员对其进行研究,并且对该系统经过了无数次改进,超级账本项目(Hyperledger)最初也是用来改善比特币的底层技术,最终由Linux基金会组织发展起来.       开放 ...

  3. zz阿里妈妈深度树检索技术(TDM)及应用框架的探索实践

    分享嘉宾:何杰 阿里妈妈 高级算法专家 编辑整理:孙锴 内容来源:DataFun AI Talk 出品社区:DataFun 注:欢迎转载,转载请注明出处 导读:阿里妈妈是阿里巴巴集团旗下数字营销的大中 ...

  4. Delphi深度探索-CodeSite应用指南

    Delphi深度探索-CodeSite应用指南 Delphi虽然为我们提供极其强大的调试功能,查找Bug仍然是一项艰巨的工作,通常我们写代码和调试代码的所消耗的时间是大致相同的,甚至有可能更多.为了减 ...

  5. Android深度探索(卷1)HAL与驱动开发 虚拟环境的安装

    前言: 最近在看<Android深度探索(卷1)HAL与驱动开发>安装随书带的虚拟环境浪费了很多时间,说是虚拟环境的安装倒不如说是虚拟环境的导入,其实没什么技术含量,也没有什么复杂的,只是 ...

  6. 侯捷STL学习(七)--深度探索vector&&array

    layout: post title: 侯捷STL学习(七) date: 2017-06-13 tag: 侯捷STL --- 第十六节 深度探索vector vector源码剖析 vector内存2倍 ...

  7. Android深度探索总结

    Android深度探索前四章总结 通过这几章的学习真实体会到“移植”的概念:为特定设备定制Android的过程,但是移植的过程中开发最多的就是支持各种硬件设备的Linux驱动程序,本章对Android ...

  8. Pascal Hexagrammum Mysticum 的深度探索

        PASCAL . Hexagrammum Mysticum . (六角迷魂图) . 的深度探索 . 英中对比.英文蓝色,译文黑色,译者补充说明用紫红色 (已校完,但尚未定稿,想再整理并补充内容 ...

  9. 读书笔记《深度探索c++对象模型》 概述

    <深度探索c++对象模型>这本书是我工作一段时间后想更深入了解C++的底层实现知识,如内存布局.模型.内存大小.继承.虚函数表等而阅读的:此外在很多面试或者工作中,对底层的知识的足够了解也 ...

随机推荐

  1. CALL FUNCTION 'BAPI_PO_CREATE1' 相关报错

    *&---------------------------------------------------------------------**& Report  ZQJ06*&am ...

  2. spring-boot5

    Spring Boot集成MyBatis: (1)新建maven project;取名为:spring-boot-mybatis (2)在pom.xml文件中引入相关依赖: (3)创建启动类App.j ...

  3. oracle删除重复数据只保留一条

    -- 如表role_user的数据 ROLEID USERID -- 删除相同记录只剩下一条记录 根据两个字段查询重复数据 (roleid,userid) ) 删除重复数据只保留一条 delete f ...

  4. BZOJ 2021 [Usaco2010 Jan]Cheese Towers:dp + 贪心

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2021 题意: John要建一个奶酪塔,高度最大为m. 他有n种奶酪.第i种高度为h[i]( ...

  5. js中使用对象变量的两种方式

    function Person(){ this.a=function(){ window.alert("a"); } this.b=function(){ window.alert ...

  6. 30个Jquery灯箱插件

    jQuery 是非常流行的JS框架,其俨然已成了开发者的必备工具,其中的jQuery Lightbox插件更是为广大开发者所喜爱.它惊人的特征之一是jQuery Lightbox插件有很多变化. 下面 ...

  7. Redis常用数据结构和操作

    1.String 存入字符类型 Set name luowen 设置name = luowen 存储 Get name 获取设置好的name的值 Setnx name luowen 设置name键值为 ...

  8. python学习笔记:第五天( 列表、元组)

    Python3 列表 序列是Python中最基本的数据结构.序列中的每个元素都分配一个数字 - 它的位置,或索引,第一个索引是0,第二个索引是1,依此类推. Python有6个序列的内置类型,但最常见 ...

  9. 揭秘FaceBook Puma演变及发展——FaceBook公司的实时数据分析平台是建立在Hadoop 和Hive的基础之上,这个根能立稳吗?hive又是sql的Map reduce任务拆分,底层还是依赖hbase和hdfs存储

    在12月2日下午的“大数据技术与应用”分论坛的第一场演讲中,来自全球知名互联网公司——FaceBook公司的软件工程师.研发经理邵铮就带来了一颗重磅炸弹,他将为我们讲解FaceBook公司的实时数据处 ...

  10. Log4Net的使用之winform

    当我们将asp程序部署到远程服务器上的时候,如果遇到程序错误,如何能快速的判断我们程序的错误呢.所以-->Log4Net作为记录日志的一大神器,不得不学会熟练使用啊!没有那么多的原理,照猫画虎的 ...