文章目录
先来点免责说明,我不能保证下面的内容都对,算是我最近看Fabric源码的成果和总结。我会从Fabric处理交易的流程来介绍Fabric各个节点之间的通讯关系。
Peer节点,可以分为:
Orderer节点:提供排序服务的节点,主要接收客户端的Broadcast消息接收交易,并响应Leader节点的Deliver消息分发区块
客户端节点:客户端,它将发起交易,通过fabric-sdk实现
客户端初始化一个Transaction
客户端A调用SDK,产生transaction proposal,它包含调用链码所需的参数,将proposal发送给该链码的背书策略所需要的Endorser节点。
这块目前,我还没有过多涉及。
背书节点验证proposal并模拟事务,返回背书结果

检测proposal response
客户端合并背书到事务中,并且将事务发送给排序服务,完成排序上链
交易中包含读写集、背书节点的签名和channel ID,排序节点无需完成最终检查任务,只需要把事务按时间和channel进行排序,并且创建事务block
区块分发,事务被验证和提交
修改账本

gRPC是google开发的go语言RPC框架,它会采用ProtoBuffer的形式传输数据,跟http采用的json不同,二进制效率更高,还有很多细节我都不是很清楚。但是关键的内容在于grpc可以注册服务。发送端使用对应的Client发送消息,消息中会指明服务名、ServiceDesc来定位一个处理器;接收端则会接收消息,分派给合适的处理器处理。
举一个Gossip服务的例子
// 服务描述
var _Gossip_serviceDesc = grpc.ServiceDesc{
ServiceName: "gossip.Gossip",
HandlerType: (*GossipServer)(nil),
Streams: []grpc.StreamDesc{
{
StreamName: "GossipStream",
Handler: _Gossip_GossipStream_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "gossip/message.proto",
}
// 服务注册,把服务描述和对应的服务注册到grpc服务器中
func RegisterGossipServer(s *grpc.Server, srv GossipServer) {
s.RegisterService(&_Gossip_serviceDesc, srv)
}
// grpc客户端
type GossipClient interface {
// GossipStream is the gRPC stream used for sending and receiving messages
GossipStream(ctx context.Context, opts ...grpc.CallOption) (Gossip_GossipStreamClient, error)
}
// 一个实现方法
func (c *gossipClient) GossipStream(ctx context.Context, opts ...grpc.CallOption) (Gossip_GossipStreamClient, error) {
// 指定了对应的服务名和ServiceDesc
stream, err := c.cc.NewStream(ctx, &_Gossip_serviceDesc.Streams[0], "/gossip.Gossip/GossipStream", opts...)
// 返回了用于传输的连接客户端
x := &gossipGossipStreamClient{stream}
return x, nil
}
// 连接客户端
type Gossip_GossipStreamClient interface {
Send(*Envelope) error // 它将用于传输pb消息
Recv() (*Envelope, error) // 好像因为rpc支持双向传输,client也是可以接收消息的
grpc.ClientStream
}
// grpc服务端
type GossipServer interface {
// GossipStream is the gRPC stream used for sending and receiving messages
GossipStream(Gossip_GossipStreamServer) error
}
// 处理器做的事情就是,调用注册入的grpc服务对应的方法,并传入连接服务器这个参数
func _Gossip_GossipStream_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(GossipServer).GossipStream(&gossipGossipStreamServer{stream})
}
// 连接服务器
type Gossip_GossipStreamServer interface {
Send(*Envelope) error
Recv() (*Envelope, error) // 它将用于接收pb消息
}
这个大概就是grpc服务器的简单应用,我们之后讨论的所有通讯关系都是基于grpc的,所有通讯的起点都是找到对应的服务,然后看它会进行怎样的处理,这也是我阅读源码的方式。
根据第2部分中的交易流程讲解期间的通讯。其中步骤1、3大多涉及客户端部分,步骤4涉及排序服务,我暂时还没有搞明白,只能直接讲解它们之间的通讯。
流程1、2都是关于客户端发送TransactionProposal给Endorser节点,并进行背书服务。
大致流程如下:

流程3没有太多可以讲的,也基本不涉及节点间的通讯。但是流程4中,交易会由客户端通过AtomicBroadcast服务向Orderer节点提交
大致的流程如下:
流程5主要完成前两项工作,因为这个过程和流程6在逻辑和代码上非常接近,我就一起讲了
其中步骤3、4是流程6要完成的主要任务之一,这里算是预告。然后步骤2不涉及通讯,就不多讲了。
调用Deliver服务的流程大致如下:
Leader节点在Channel初始化的过程中,会启动Deliver客户端,它会启动一个新线程来不断请求新区块,单个循环的流程:

流程6的讲解会不同于上面,因为Gossip服务过于复杂,远远不止区块添加这一个功能,各个功能之间不会有明显的时序性,我们将侧重点放在Gossip服务本身上,先分析其起分发器作用的comm模块,再根据Gossip消息类型介绍功能,最后整合来讲节点之间添加区块并达成一致的过程,顺便提一下,Gossip服务主要用于Peer之间。
Comm模块主要负责两项任务:1. 处理收到的GossipMessage 2. 生成点对点的连接,并发送GossipMessage
1、处理收到的GossipMessage
Comm模块的核心类CommImpl是Gossip服务的实现类,它会负责处理所有来自其他节点的GossipMessage,它的处理方式很类似gRPC服务器,也是采用类似订阅发布(观察者模式)的设计模式。
它允许其他模块订阅一些类型的GossipMessage,Comm在收到对应的GM后,就会把消息发布给对应的模块,让其处理消息。不过,它倒不是简单地每个模块订阅一个类型的GossipMessage,具体的订阅过程可以看下面的GossipMessage的处理树,不过重点不在上面。
GossipMessage有以下类型,还可以根据功能稍微划分一下:
然后我再放一个各种GossipMessage被各个模块处理的树形结构
2、生成点对点的连接,并发送GossipMessage
Comm生成连接的方式是:
GossipMessage最特别的就是GM_Empty和GM_Conn,GM_Empty会通过Ping服务来发送和处理,GM_Conn虽然也通过GossipStream服务发送,但是它是直接通过调用服务发送的。
创建连接后,其他所有GossipMessage将会由连接进行发送,连接可以视为对GossipStream服务和点对点连接的一个封装。
Comm模块会负责对连接管理,它会以pkiID2Conn的方式去保存连接,必要时关闭对应的连接。
节点发现服务的作用就是让节点发现网络中有哪些其他节点。
一个节点会维护其他节点的状态,状态有三种:活的、死的、不认识的。整个节点发现服务就是在维护节点的状态,这个过程是没有很强的时序性的,我会一点点地介绍该模块会改变节点的状态的机制。
在模块的基础机制和GM_AliveMsg的作用下,节点的状态会有以下的变化情况:

本节点会周期性地向其他节点发送GM_AliveMsg,通知本节点活着,其他节点收到AliveMsg除了会修改对应节点的状态外,还会继续散播该消息
本节点会周期性地向被视为死亡的节点发送GM_Empty消息来Ping这些节点,查看死活,之后做什么看下一条
本节点可以向其他节点发送GM_MemReq,请求其他节点当前存储的节点存活信息,收到GM_MemRes后,可以得知其所知的存活节点和死亡节点,对于存活节点,它会将这些存活信息视为GM_AliveMsg处理,而对于死亡节点,只会认识一下,添加到死亡节点中。此外,当然都能收到它的GM_MemRes,该节点必然是存活的。
GM_MemReq会在以下三种情况下发送:
在加入这些机制后,状态转换会变成

该服务发现的一个重要应用就是Gossip服务的散播(Gossip)功能,它的实现方式就是通过discovery服务找到节点,然后再通过comm模块下的点对点连接来对所有找到的节点散播消息。
区块散播是区块链达成最终一致性的最核心的方式,但是它过程并不复杂:

它往往能解决大部分一致性的问题,它会被动地接收其他节点的区块;此外,还有两种主动取得区块的机制,它们就将在后面介绍:
区块拉取的作用就是为了账本的最终一致性,它的工作流程也向上面说的一样,先问别人有啥区块,从中挑选出自己要的,再让别人发给你。

除了这个流程之外,我们要关心的问题就是puller是如何维护它存储的GM_DataMsg的。通常puller中的DataMsg和msgStore是同步的,
DataMsg添加会发生在:
在将DataMsg删除之前,我们首先得知道MessageStore,它的特点是存储的数据会过期,且可以过期回调函数,其实之前的Discovery模块中实现从死亡到不认识也是靠这个过期回调。DataMsg的删除靠的就是MsgStore的过期功能,在msgStore的消息过期后,它同时也会删除puller中的消息
因为之前都是在讲把DataMsg交给state子模块写入账本,还有各种达成数据最终一致性的方法,所以我们先把这块内容插队讲一下,让节点状态信息共享的内容放后面。
1、写入账本功能
首先,我们先讲state子模块是如何将区块写入账本的,准确的说是state子模块除了调用kvLedger来将区块写入账本以外还提供了其他什么功能,kvLedger是实际上将区块写入账本的实现类。state子模块主要为该功能提供了缓存的功能,它能提前接收后续的区块,采用滑动窗口的方式接收和提交区块。

当黄色对应的区块到达时,窗口即可右移,将该区块写入到账本中,不过此处调用的是privdata子模块的coordinator,它也仅仅是为kvLedger提供加强方法,coordinator会在私有数据部分介绍。
2、反熵功能
该功能就像之前说的,向其他节点索要账本中比自己高的部分,该过程会周期性地执行。

节点状态信息分享只是channel子模块的一个功能,channel子模块会统筹其他子模块完成与该channel相关的功能,但是此处我们只考虑节点状态信息分享。
它的机制比较类似节点发现服务,功能也是提供其他节点的信息,AliveMsg主要提供了节点是否存活、节点账本高度、endpoint、pkiID、系统时间等信息,而StateInfoMsg会提供更多信息,例如安装了哪些链码,是否属于channel(stateInfo是channel作用域下的,由gossipChannel管理)等。
节点状态信息共享有着和节点发现服务很类似的机制:
因为和节点发现服务很类似,我连画图都懒得画了。。。
先科普一下什么叫私有数据。。。大概,这块我之前看到就跳过的,最近才学,而且我还从没有应用过。
transient(瞬态)字段中。瞬态数据存储( transient data store ,节点的本地临时存储)中。它们根据组织集合的策略将私有数据通过gossip分发给授权的 Peer 节点。瞬态数据存储 ,以确定它们是否在链码背书的时候已经接收到了私有数据。如果没有的话,它们会尝试从其他已授权节点那里拉取私有数据,然后对照公共区块上的 hash 来验证私有数据并提交交易和区块。当验证或提交结束后,私有数据会被移动到这些节点私有数据库和私有读写存储的副本中。随后 瞬态数据存储 中存储的这些私有数据会被删除。privdata子模块为了完成上面的数据流程,会进行以下的流程:

当然事事不可能那么顺心,在提交区块的阶段会有以下情况,我用流程图来表示:

大致情况分为三种:
上面的时序图就是情况1,现在再讲讲其他两种情况
情况2:

情况3:

这基本就是privdata子模块实现私有数据流动的过程。
但是还有一个点要讲一下,那就是coordinator,之前我们在state子模块中提到,state提交区块的功能只是提供一个缓存的功能,实际上调用的coordinator的StoreBlock功能,那么coordinator做了什么呢?
coordinator主要提供了取得私有数据的功能,其中的情况1、2就是coordinator做到的,之后它将调用PeerLedger.CommitLegacy从而调用kvLedger来真正地将区块写入到账本中

这篇文章,不是啥官方的东西,只是我自己在阅读源码后整理出来的,除了有些图和文字来自官网,其他基本都是我自己胡诌出来的,大家有问题的话可以在评论区提出来。。。
我的问题的一个例子是体育游戏。一场体育比赛有两支球队,一支主队和一支客队。我的事件记录模型如下:classTeam"Team"has_one:away_team,:class_name=>"Team"end我希望能够通过游戏访问一个团队,例如:Game.find(1).home_team但我收到一个单元化常量错误:Game::team。谁能告诉我我做错了什么?谢谢, 最佳答案 如果Gamehas_one:team那么Rails假设您的teams表有一个game_id列。不过,您想要的是games表有一个team_id列,在这种情况下
📢博客主页:https://blog.csdn.net/weixin_43197380📢欢迎点赞👍收藏⭐留言📝如有错误敬请指正!📢本文由Loewen丶原创,首发于CSDN,转载注明出处🙉📢现在的付出,都会是一种沉淀,只为让你成为更好的人✨文章预览:一.分辨率(Resolution)1、工业相机的分辨率是如何定义的?2、工业相机的分辨率是如何选择的?二.精度(Accuracy)1、像素精度(PixelAccuracy)2、定位精度和重复定位精度(RepeatPrecision)三.公差(Tolerance)四.课后作业(Post-ClassExercises)视觉行业的初学者,甚至是做了1~2年
我想合并多个事件记录关系例如,apple_companies=Company.where("namelike?","%apple%")banana_companies=Company.where("namelike?","%banana%")我想结合这两个关系。不是合并,合并是apple_companies.merge(banana_companies)=>Company.where("namelike?andnamelike?","%apple%","%banana%")我要Company.where("名字像?还是名字像?","%apple%","%banana%")之后,我会写代
我有一个简单的问题,与关联有关。我有一个书的模型,它有_onereservation。预订属于_书本。我想在预订Controller的创建方法中确保在预订时没有预订一本书。换句话说,我需要检查该书是否存在任何其他预订。我该怎么做?编辑:Aaa我做到了,感谢大家的提示,学到了一些新东西。当我尝试提供的解决方案时,出现no_method错误或nil_class等。这让我开始思考,我尝试处理的对象根本不存在。Krule给了我使用book.find的想法,所以我尝试使用它。最终我得到了它的工作:book=Book.find_by_id(reservation_params[:book_id])
我有一组名为Tasks和Posts的资源,它们之间存在has_and_belongs_to_many(HABTM)关系。还有一个连接它们的值的连接表。create_table'posts_tasks',:id=>falsedo|t|t.column:post_id,:integert.column:task_id,:integerend所以我的问题是如何检查特定任务的ID是否存在于从@post.tasks创建的数组中?irb(main):011:0>@post=Post.find(1)=>#@post.tasks=>[#,#]所以我的问题是,@post.tasks中是否存在"@task
根据我目前的理解,如果我必须描述Rails应用程序的各个组件如何协同工作以响应请求,我会说以下内容:1)路由确定哪些请求URL映射到哪些Controller方法。2)Controller方法从模型中获取信息并将该信息(以全局变量的形式)传递给相应的View模板。3)View模板使用存储在全局变量中的数据来构造最终响应。在上面的解释中,几个组件之间的关系是明确的,不可否认的;即:1)路由和Controller方法2)Controller方法和View模板其实上面的关系是一对一的。但是,模型类与其相邻组件类型(即Controller)的关系并不明确。是的,Controller从模型中检索信
我在目录“/home/enterprise/pkg”中有一个本地gem(enterprise-0.0.1.gem)。它依赖于active_directorygem(v1.5.5),这是在它的enterprise.gemspec文件中指定的,如下所示:-gem.add_dependency("active_directory")在我的应用程序的Gemfile中,我添加了以下行:-gem'enterprise','0.0.1',path=>'/home/enterprise/pkg'当我做的时候bundleinstall在我的应用程序的源目录中,只安装了企业gem。因此,我遇到了引用act
我正在使用devise登录omniauth,authid。当用户登录时,我得到user_info:name:RiccardoTacconilast_name:Tacconiemail:email@gmail.comfirst_name:Riccardouid:https://www.google.com/accounts/o8/id?id=xxxxxxxxxprovider:google_apps我找到了一个插件:http://stakeventures.com/articles/2009/10/06/portable-contacts-in-ruby获取Google通讯录。我只需要使
我正在构建这个图书馆应用程序,它有3个类。国家、图书馆和书籍。国家有许多图书馆,图书馆属于一个国家。图书馆有很多书,书是嵌入图书馆的。但是,当我执行此auto_pick_job时,我们到达top_free_book并调用library.state。由于某种原因,library.state为nil。我希望恢复状态但没有骰子。我调用和创建库的方式如下。所以图书馆将永远属于一个现有的国家。state=Stats.find(x)library=state.libaries.new(info)library.save_optimistic!我也很感激使用Struct的关系帮助。classStat
我有一个看起来像这样的类:classFoo在测试#nasty_bars_present?我想编写一个rspec测试来对bars关联进行stub,但允许where自然执行。像这样的东西:describe"#nasty_bars_present?"docontext"withnastybars"dobefore{foo.stub(:bars).and_return([mock(Bar,bar_type:"Nasty")])}it"shouldreturntrue"doexpect(foo.nasty_bars_present?).tobe_trueendendend上面的测试给出了一个关于