fabric1.0学习笔记(4)

fabric1.0学习笔记(1)
fabric1.0学习笔记(2)
fabric1.0学习笔记(3)
fabric1.0学习笔记(4)
fabric1.0学习笔记(5)
fabric1.0学习笔记(6)

在学习笔记(3)中主要了解了链对象chainsupport类、区块切割器及solo共识模式的实现,本篇文章主要了解一下交易收集、区块扩散的实现。然后对共识机制进行简单的总结,接着了解一下账本存储的相关概念。

  • 交易收集
    代码在fabric/orderer/common/broadcast路径下
// Handle starts a service thread for a given gRPC connection and services the broadcast connection
func (bh *handlerImpl) Handle(srv ab.AtomicBroadcast_BroadcastServer) error {
	logger.Debugf("Starting new broadcast loop")
	for {
		'交易接收(获取消息)'
		msg, err := srv.Recv()
		if err == io.EOF {
			logger.Debugf("Received EOF, hangup")
			return nil
		}
		if err != nil {
			logger.Warningf("Error reading from stream: %s", err)
			return err
		}
		'消息体校验'
		payload, err := utils.UnmarshalPayload(msg.Payload)
		if err != nil {
			logger.Warningf("Received malformed message, dropping connection: %s", err)
			return srv.Send(&ab.BroadcastResponse{Status: cb.Status_BAD_REQUEST})
		}

		if payload.Header == nil {
			logger.Warningf("Received malformed message, with missing header, dropping connection")
			return srv.Send(&ab.BroadcastResponse{Status: cb.Status_BAD_REQUEST})
		}
		'解析消息体中的通道头'
		chdr, err := utils.UnmarshalChannelHeader(payload.Header.ChannelHeader)
		if err != nil {
			logger.Warningf("Received malformed message (bad channel header), dropping connection: %s", err)
			return srv.Send(&ab.BroadcastResponse{Status: cb.Status_BAD_REQUEST})
		}
		'如果是配置交易则对消息体进行二次校验'
		if chdr.Type == int32(cb.HeaderType_CONFIG_UPDATE) {
			logger.Debugf("Preprocessing CONFIG_UPDATE")
			msg, err = bh.sm.Process(msg)
			if err != nil {
				logger.Warningf("Rejecting CONFIG_UPDATE because: %s", err)
				return srv.Send(&ab.BroadcastResponse{Status: cb.Status_BAD_REQUEST})
			}

			err = proto.Unmarshal(msg.Payload, payload)
			if err != nil || payload.Header == nil {
				logger.Criticalf("Generated bad transaction after CONFIG_UPDATE processing")
				return srv.Send(&ab.BroadcastResponse{Status: cb.Status_INTERNAL_SERVER_ERROR})
			}

			chdr, err = utils.UnmarshalChannelHeader(payload.Header.ChannelHeader)
			if err != nil {
				logger.Criticalf("Generated bad transaction after CONFIG_UPDATE processing (bad channel header): %s", err)
				return srv.Send(&ab.BroadcastResponse{Status: cb.Status_INTERNAL_SERVER_ERROR})
			}

			if chdr.ChannelId == "" {
				logger.Criticalf("Generated bad transaction after CONFIG_UPDATE processing (empty channel ID)")
				return srv.Send(&ab.BroadcastResponse{Status: cb.Status_INTERNAL_SERVER_ERROR})
			}
		}
		'根据消息头中的道道id获取一个support对象'
		support, ok := bh.sm.GetChain(chdr.ChannelId)
		if !ok {
			logger.Warningf("Rejecting broadcast because channel %s was not found", chdr.ChannelId)
			return srv.Send(&ab.BroadcastResponse{Status: cb.Status_NOT_FOUND})
		}

		logger.Debugf("[channel: %s] Broadcast is filtering message of type %s", chdr.ChannelId, cb.HeaderType_name[chdr.Type])

		// Normal transaction for existing chain
		'将消息发送到support的过滤器中进行第一次过滤,在区块切割中会有第二次过滤'
		_, filterErr := support.Filters().Apply(msg)

		if filterErr != nil {
			logger.Warningf("[channel: %s] Rejecting broadcast message because of filter error: %s", chdr.ChannelId, filterErr)
			return srv.Send(&ab.BroadcastResponse{Status: cb.Status_BAD_REQUEST})
		}
		'消息入列'
		if !support.Enqueue(msg) {
			return srv.Send(&ab.BroadcastResponse{Status: cb.Status_SERVICE_UNAVAILABLE})
		}
		'如果日志级别为debug输出成功内容'
		if logger.IsEnabledFor(logging.DEBUG) {
			logger.Debugf("[channel: %s] Broadcast has successfully enqueued message of type %s", chdr.ChannelId, cb.HeaderType_name[chdr.Type])
		}
		'处理成功返回'
		err = srv.Send(&ab.BroadcastResponse{Status: cb.Status_SUCCESS})
		if err != nil {
			logger.Warningf("[channel: %s] Error sending to stream: %s", chdr.ChannelId, err)
			return err
		}
	}
}

整个过程大部分是对交易的校验,如果是配置交易会校验两次,校验完通过交易头获取链对象(此处还有一次校验,区块切割仍然有校验),再将校验完的交易通过链对象加入消息队列。

  • 区块扩散
    代码在fabric/orderer/common/deliver路径下
func (ds *deliverServer) Handle(srv ab.AtomicBroadcast_DeliverServer) error {
	logger.Debugf("Starting new deliver loop")
	for {
		logger.Debugf("Attempting to read seek info message")
		'获取消息即交易'
		envelope, err := srv.Recv()
		if err == io.EOF {
			logger.Debugf("Received EOF, hangup")
			return nil
		}

		if err != nil {
			logger.Warningf("Error reading from stream: %s", err)
			return err
		}
		'消息体校验'
		payload, err := utils.UnmarshalPayload(envelope.Payload)
		if err != nil {
			logger.Warningf("Received an envelope with no payload: %s", err)
			return sendStatusReply(srv, cb.Status_BAD_REQUEST)
		}

		if payload.Header == nil {
			logger.Warningf("Malformed envelope received with bad header")
			return sendStatusReply(srv, cb.Status_BAD_REQUEST)
		}
		'获取与链相关的消息头'
		chdr, err := utils.UnmarshalChannelHeader(payload.Header.ChannelHeader)
		if err != nil {
			logger.Warningf("Failed to unmarshal channel header: %s", err)
			return sendStatusReply(srv, cb.Status_BAD_REQUEST)
		}
		'根据消息头中的通道ID获取链的对象即chainsupport对象'
		chain, ok := ds.sm.GetChain(chdr.ChannelId)
		if !ok {
			// Note, we log this at DEBUG because SDKs will poll waiting for channels to be created
			// So we would expect our log to be somewhat flooded with these
			logger.Debugf("Rejecting deliver because channel %s not found", chdr.ChannelId)
			return sendStatusReply(srv, cb.Status_NOT_FOUND)
		}
		'检查链是否有错误'
		erroredChan := chain.Errored()
		select {
		case <-erroredChan:
			logger.Warningf("[channel: %s] Rejecting deliver request because of consenter error", chdr.ChannelId)
			return sendStatusReply(srv, cb.Status_SERVICE_UNAVAILABLE)
		default:

		}
		'获取最新配置文件的序列号'
		lastConfigSequence := chain.Sequence()
		'对链配置信息的校验'
		sf := sigfilter.New(policies.ChannelReaders, chain.PolicyManager())
		result, _ := sf.Apply(envelope)
		if result != filter.Forward {
			logger.Warningf("[channel: %s] Received unauthorized deliver request", chdr.ChannelId)
			return sendStatusReply(srv, cb.Status_FORBIDDEN)
		}
		'解析(查询请求)的内容'
		seekInfo := &ab.SeekInfo{}
		if err = proto.Unmarshal(payload.Data, seekInfo); err != nil {
			logger.Warningf("[channel: %s] Received a signed deliver request with malformed seekInfo payload: %s", chdr.ChannelId, err)
			return sendStatusReply(srv, cb.Status_BAD_REQUEST)
		}

		if seekInfo.Start == nil || seekInfo.Stop == nil {
			logger.Warningf("[channel: %s] Received seekInfo message with missing start or stop %v, %v", chdr.ChannelId, seekInfo.Start, seekInfo.Stop)
			return sendStatusReply(srv, cb.Status_BAD_REQUEST)
		}

		logger.Debugf("[channel: %s] Received seekInfo (%p) %v", chdr.ChannelId, seekInfo, seekInfo)
		'查询的起始位置区块的指针'
		cursor, number := chain.Reader().Iterator(seekInfo.Start)
		var stopNum uint64
		switch stop := seekInfo.Stop.Type.(type) {
		case *ab.SeekPosition_Oldest:
			stopNum = number
		case *ab.SeekPosition_Newest:
			stopNum = chain.Reader().Height() - 1
		case *ab.SeekPosition_Specified:
			stopNum = stop.Specified.Number
			if stopNum < number {
				logger.Warningf("[channel: %s] Received invalid seekInfo message: start number %d greater than stop number %d", chdr.ChannelId, number, stopNum)
				return sendStatusReply(srv, cb.Status_BAD_REQUEST)
			}
		}

		for {
			'判断orderer节点是否需要等待'
			'如果需要等待'
			if seekInfo.Behavior == ab.SeekInfo_BLOCK_UNTIL_READY {
				select {
				case <-erroredChan:
					logger.Warningf("[channel: %s] Aborting deliver request because of consenter error", chdr.ChannelId)
					return sendStatusReply(srv, cb.Status_SERVICE_UNAVAILABLE)
				case <-cursor.ReadyChan():
				}
			} else { '如果错误'
				select {
				case <-cursor.ReadyChan():
				default:
					return sendStatusReply(srv, cb.Status_NOT_FOUND)
				}
			}
			'获取最新配置文件的序列号'
			currentConfigSequence := chain.Sequence()
			'校验配置更改'
			if currentConfigSequence > lastConfigSequence {
				lastConfigSequence = currentConfigSequence
				sf := sigfilter.New(policies.ChannelReaders, chain.PolicyManager())
				result, _ := sf.Apply(envelope)
				if result != filter.Forward {
					logger.Warningf("[channel: %s] Client authorization revoked for deliver request", chdr.ChannelId)
					return sendStatusReply(srv, cb.Status_FORBIDDEN)
				}
			}
			'循环读取区块'
			block, status := cursor.Next()
			if status != cb.Status_SUCCESS {
				logger.Errorf("[channel: %s] Error reading from channel, cause was: %v", chdr.ChannelId, status)
				return sendStatusReply(srv, status)
			}

			logger.Debugf("[channel: %s] Delivering block for (%p)", chdr.ChannelId, seekInfo)
			'把区块发送给组织的主节点'
			if err := sendBlockReply(srv, block); err != nil {
				logger.Warningf("[channel: %s] Error sending to stream: %s", chdr.ChannelId, err)
				return err
			}
			'读到结束区块的位置退出循环'
			if stopNum == block.Header.Number {
				break
			}
		}
		'返回读取结果'
		if err := sendStatusReply(srv, cb.Status_SUCCESS); err != nil {
			logger.Warningf("[channel: %s] Error sending to stream: %s", chdr.ChannelId, err)
			return err
		}

		logger.Debugf("[channel: %s] Done delivering for (%p), waiting for new SeekInfo", chdr.ChannelId, seekInfo)
	}
}

首先接受查询请求,然后获取查询请求的链对象,通过链对象对请求中的配置信息进行校验,校验通过后解析查询请求中的内容,接着循环将所要查询的区块发送给各组织的主节点,在每一遍循环中都会校验一次链的配置信息是否发生了改变,一旦改变就会停止发送返回错误信息。

以上学习完了orderer节点的几个主要功能及主要类的代码包括manager、chainsupport、区块切割、solo模式实现、交易收集、区块分发。orderer节点的整个过程就是在实现一个grpc服务,首先载入配置信息、初始化日志级别、启动profile服务、根据配置信息初始化一个grpc服务;接着初始化本地msp并注册签名者、实例化一个多链管理者、实例化服务(交易收集和区块分发服务)。再把grpc服务和交易收集和区块分发服务绑定在一起,最后启动grpc服务,orderer服务就算启动了。下图是对orderer服务功能模块的简单梳理
请添加图片描述

一、接着简单了解一下peer(记账节点)节点账本存储中的概念

peer节点的账本存储与之前的orderer节点的账本存储不同,orderer节点只是临时存储,最后会发给各个peer节点存储,所以也是没有保存状态的。而peer节点就需要保存世界状态来反映用户信息了。

整个peer账本存储可以分成4块:

  • 1、区块存储 通过文件系统
  • 2、状态存储 通过数据库(LevelDB/CouchDB)
  • 3、历史状态索引存储 通过数据库(LevelDB)
  • 4、区块索引存储 通过数据库(LevelDB)

首先,peer节点上的区块是以文件系统进行存储的而不是数据库;
为了保证区块信息的查询就需要用到区块索引,区块索引用的是数据库进行存储,区块具体是通过文件编号和偏移量来索引的,因为在存储的时候是很多区块一起存入一个文件中所以需要文件编号获取文件位置,通过偏移量来确定区块在文件中的具体位置。区块索引具体的内容就是区块哈希、区块编号、及交易ID与区块位置的关联;
peer节点还要存储状态数据,即区块链上的最新数据,交易是一个过程而状态数据则是交易产生的结果,具体是通过状态数据库对状态数据进行存储并随交易的产生不断更新。并且状态可以通过区块中的交易来进行重构,区块这个时候有点像数据库中的日志;
最后就是历史状态索引,它是用来记录数据在哪一个交易中被更新的(这里并没有存交易而是保存对应的交易,这么做是为了减少存储空间的开销),这样在查询某一个数据交易历史的时候就不需要从区块中一笔笔的去找,节省了很多时间。

交易读写集的概念
交易模拟产生交易读写集,在交易验证阶段会对交易读写集进行验证
交易读写集就是告诉区块链交易对哪些数据进行了读操作,对哪些数据进行了写操作或更新操作 包括读集、写集和版本号

  • 读集:读取的已经提交的状态值,这里的已提交是指“确认高度"区块之前的数据属于已提交的数据,对fabric来说就是上一个被提交的区块的状态值,这样做是为了防止区块链分叉。
  • 写集:将要更新的状态键值对或状态键值对的删除标记,这里如果涉及同一个数据的多次更新则只会保留最后一次的值,即交易是最小的原子单位不能再细分(与数据库事务的原子性相似)。
  • 版本号:由区块高度和交易编号组成,用来标记一笔交易中读的交易所对应的版本号,用于验证读操作是否有效。

交易验证:包括签名验证、权限验证等 最重要的是交易读写集的验证
交易读写集的验证就是判断交易中读集的版本号是否等于世界状态的版本号,这里的世界状态受已提交的交易影响,也受到未提交交易即当前区块中该笔交易之前的交易的影响。例如:一个区块中的两个交易1、2,1对某个键值对进行了修改,2对该修改的键值对进行读那么交易2就是一笔无效交易。可以看出区块验证保证了写优先,读到的数据一定是最新的数据。

世界状态:交易执行后的所有键的最新值,从某种意义上说fabric就是一个key-value数据库,应用与区块链交互都是通过智能合约,而fabric智能合约中最重要的两个接口就是getstate和setstate。世界状态能够提升智能合约的执行效率(不需要每次都查询区块),它是区块链中的一个快照,随时可以通过区块来进行重构,peer节点每次启动的时候就会检查世界状态与区块存储的内容是否一致。
历史数据索引(可选):记录某个键在哪一个区块的哪一条交易中被改变的,如果智能合约有需要查询数据历史就可以开启这个功能,它只记录改变的动作,不记录具体内容,读取的时候首先获取键值对的改变位置,然后再从区块中读取交易
区块存储:区块在fabric中以文件块形式存储(文件名为blockfile_xxxxxx)文件块大小和数量(6位000000~999999)一般是设定好的,如果想要修改需要重新编译peer的源代码.
区块读取:目前已实现的有区块文件流、区块流及区块迭代器这三个类,分别用于读取文件块、从文件块中读取区块、在整个链条上读取区块等。
区块索引:用于快速定位区块,查询条件(索引值)可以是区块高度、区块哈希、交易哈希等,区块的位置由区块文件编号、文件内偏移量、区块数据长度标记的(从那个文件读,从文件的哪个位置开始读,读多少的长度)
区块提交(把交易写入账本):首先把区块保存到区块文件中,peer会对区块中的交易进行验证,注意:即使是无效交易也会被存储,与区块哈希有关,因为区块哈希是在orderere产生的如果在peer处删除那么区块哈希就会改变。保存后更新区块索引及世界状态(只有有效交易才会更新世界状态),更新历史状态(可选如果智能合约有需要的话)这三步的同步非常重要,在peer节点启动时都会检查这三者是否同步,如果不一致则以区块文件为标准进行同步。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值