Hyperledger Fabric(v1.2.0)代码分析1——channel创建
Hyperledger Fabric(v1.2.0)代码分析1——channel创建
0. e2e_cli
Hyperledger Fabric提供了一个e2e的例子,该例中创建了一个基础的区块链网络,并进行了交易并查询。麻雀虽小,五脏俱全,我们可以根据e2e的例子来对Fabric区块链网络有一个基本的认识,包括网络创建流程、智能合约(chaincode)实现等。作为底层技术开发者,可以根据e2e的例子来追踪整个代码流程,从而对fabric源码结构有一个清晰的认识。
ele示例的区块链网络由以下几部分顺序执行:
0.1 Generate加密所需要的材料
本过程是根据定义了网络拓扑结构的yaml文件来产生对应关系的加密材料,其主要过程是调用Fabric编译生成的cryptogen工具,cryptogen输入为crypto-config.yaml该文件定义了网络的拓扑结构,具体内容可以查看该文件。
$CRYPTOGEN generate --config=./crypto-config.yaml
0.2 Generate ChannelArtifacts
这一步主要是生成Orderer的创世区块和一些配置transaction。
# 生成Orderer的创世块
$CONFIGTXGEN -profile TwoOrgsOrdererGenesis -channelID e2e-orderer-syschan -outputBlock ./channel-artifacts/genesis.block
# 生成Channel-create.tx
# 这是一个配置transaction,在channel创建时会用到这一步生成的transaction
$CONFIGTXGEN -profile TwoOrgsChannel -outputCreateChannelTx ./channel-artifacts/channel.tx -channelID $CHANNEL_NAME
# 生成anchor-peer update-transaction for peer0.org1
$CONFIGTXGEN -profile TwoOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/Org1MSPanchors.tx -channelID $CHANNEL_NAME -asOrg Org1MSP
# 生成anchor-peer update-transaction
$CONFIGTXGEN -profile TwoOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/Org2MSPanchors.tx -channelID $CHANNEL_NAME -asOrg Org2MSP
0.3 启动docker容器
这一步会根据docker-compose-cli.yaml文件来启动docker容器,其中包括了org1下的两个peer、org2下的两个peer、一个orderer、一个cli。cli容器是我们进行网络控制入口,在cli容器上,我们可以进行网络配置、发起交易、查询交易等操作。
CHANNEL_NAME=$CH_NAME TIMEOUT=$CLI_TIMEOUT docker-compose -f $COMPOSE_FILE -f $COMPOSE_FILE_COUCH up -d 2>&1
0.4 script脚本执行
在cli容器启动后,会执行scripts/script.sh脚本,该脚本的内容主要包括以下操作;
- createChannel
- joinChannel
- installChaincode
- instantiateChaincode
- invokeChaincode
- queryChainclde
我们对网络的的操作都是从这个脚本发出的,所以我们可以根据script.sh脚本中的步骤来分析fabric代码
1. Create Channel
Channel的创建过程是这样的:
- 首先的读取本文0.2章节生成的channel.tx,根据其中内容创建一个channel-create的transaction;
- 经过一些基本的验证签名之后,会将channel-create-transaction通过gRPC调用发送给orderer节点;
- orderer节点收到创建channel的transaction后,创建一个对应于该channel的创世块,然后将该创世块返回给cli,在后续join-channel的操作中会用到该创世块。
下面来看channel创建过程的代码是如何实现的。
channel创建是通过peer channel create [flags & args]命令来实现的,我们需要首先定位到peer->channel->create中到底干了啥。
生成的peer工具的源码位置是在${Fabric}/peer/目录下,打开该目录的下main.go源码可以看到如下内容:
mainCmd.AddCommand(version.Cmd())
mainCmd.AddCommand(node.Cmd())
mainCmd.AddCommand(chaincode.Cmd(nil))
mainCmd.AddCommand(clilogging.Cmd(nil))
mainCmd.AddCommand(channel.Cmd(nil))
在fabric中的命令行处理采用了比较流行的cobra库,以上几行代码是为peer添加几个子命令,我们这里关注的channel相关的命令,因此我们跳转进入channel相关的源码。
在${Fabric}/peer/channel目录下,是channel相关的实现,其中包含了create、join等子命令。
在文件${Fabric}/peer/channel/create.go中,找到create命令的执行函数
func createCmd(cf *ChannelCmdFactory) *cobra.Command {
createCmd := &cobra.Command{
Use: "create",
Short: "Create a channel",
Long: "Create a channel and write the genesis block to a file.",
RunE: func(cmd *cobra.Command, args []string) error {
return create(cmd, args, cf)
},
}
flagList := []string{
"channelID",
"file",
"outputBlock",
"timeout",
}
attachFlags(createCmd, flagList)
return createCmd
}
当执行channel create命令时,会进入create(cmd, args, cf)函数,该函数位于源码文件${Fabric}/peer/channel/create.go最下面,内容如下:
func create(cmd *cobra.Command, args []string, cf *ChannelCmdFactory) error {
// the global chainID filled by the "-c" command
if channelID == common.UndefinedParamValue {
return errors.New("must supply channel ID")
}
// Parsing of the command line is done so silence cmd usage
cmd.SilenceUsage = true
var err error
if cf == nil {
cf, err = InitCmdFactory(EndorserNotRequired, PeerDeliverNotRequired, OrdererRequired)
if err != nil {
return err
}
}
return executeCreate(cf)
}
create(cmd, args, cf)函数首先检查了是否提供了channelID,之后调用executeCreate(cf)。executeCreate(cf)函数进行了以下几部分工作:
- sendCreateChainTransaction(cf),发送transaction给order
- getGenesisBlock(cf),获取创世块
- 将上一步获取的block尽心字节化编码
- 写文件,将获取的创世块写到文件里保存
func executeCreate(cf *ChannelCmdFactory) error {
// 发送transaction到orderer
err := sendCreateChainTransaction(cf)
if err != nil {
return err
}
// 从orderer获取生成的channel对应的创世块
block, err := getGenesisBlock(cf)
if err != nil {
return err
}
// 将block字节化编码
b, err := proto.Marshal(block)
if err != nil {
return err
}
// 字节序列化后的block写入文件中,后面join-channel时会用到该文件
file := channelID + ".block"
if outputBlock != common.UndefinedParamValue {
file = outputBlock
}
err = ioutil.WriteFile(file, b, 0644)
if err != nil {
return err
}
return nil
}
看到这个地方,我们已经知道了创建channel时主要干了啥,后面我们一次分析每一项是具体怎么实现的。
1.1 sendCreateChainTransaction(cf)
首先是发送transaction,我们跳转进入该函数。
func sendCreateChainTransaction(cf *ChannelCmdFactory) error {
var err error
var chCrtEnv *cb.Envelope
if channelTxFile != "" {
if chCrtEnv, err = createChannelFromConfigTx(channelTxFile); err != nil {
return err
}
} else {
if chCrtEnv, err = createChannelFromDefaults(cf); err != nil {
return err
}
}
if chCrtEnv, err = sanityCheckAndSignConfigTx(chCrtEnv); err != nil {
return err
}
var broadcastClient common.BroadcastClient
broadcastClient, err = cf.BroadcastFactory()
if err != nil {
return errors.WithMessage(err, "error getting broadcast client")
}
defer broadcastClient.Close()
err = broadcastClient.Send(chCrtEnv)
return err
}
sendCreateChainTransaction()函数以此做了如下工作:
- 根据传入的channel.tx文件创建transaction格式封装chCrtEnv,如果没有提供该文件的话,就根据默认情况创建chCrtEnv;
- 对于生成的chCrtEnv进行一系列检查,并生成签名;
- broadcastClient.Send(chCrtEnv),将chCrtEnv发送给Orderer。
这里需要讲一下,对于channel相关的配置管理通过ConfigUpdate数据结构来实现的,创建channel的过程就是一个特殊的ConfigUpdate,当发现ConfigUpdate对应的channel不存在时,就会创建channel。关于channel配置相关的数据类型可以参考博文(https://www.cnblogs.com/gexin/p/9332719.html)。
后面的具体创建工作的是在orderer节点上进行的,接下来将目光转移到orderer实现代码中。
1.2 Orderer创建channel
Orderer节点会启动gRPC server,用于帧听client过来的broadcast 消息。
消息的处理函数位于orderer\common\broadcast\broadcast.go中,如下所示:
// Handle starts a service thread for a given gRPC connection and services the broadcast connection
func (bh *handlerImpl) Handle(srv ab.AtomicBroadcast_BroadcastServer) error {
// 一系列检查
// 检查代码略过
// 根据msg解析出其中的channelHeader
chdr, isConfig, processor, err := bh.sm.BroadcastChannelSupport(msg)
if !isConfig {
// 过来的消息不是config消息
} else { // isConfig
// 该代码部分是配置transaction的处理部分
// 应用msg中的配置到当前配置
config, configSeq, err := processor.ProcessConfigUpdateMsg(msg)
if err != nil {
// 错误处理
}
// 然后将config应用
err = processor.Configure(config, configSeq)
if err != nil {
// 错误处理
}
}
err = srv.Send(&ab.BroadcastResponse{Status: cb.Status_SUCCESS})
if err != nil {
// 错误处理
}
}
由以上代码,我们可以看到在gRPC的处理函数中的做了以下几个工作:
- BroadcastChannelSupport(msg),该函数定义在
orderer/common/mutichannel/registar.go中,这一步骤是根据chainID去找到map已有的channelSupport类型变量(如果没有就直接新建了一个)并判断是否是config类型的transaction。 - ProcessConfigUpdateMsg(msg),该函数定义在
orderer/common/msgprocessor/systemchannel.go中,对于一个创建channel的消息而言,该过程会返回一个ORDERER_TRANSACTION。 - Configure(config, configSeq),该函数做的就是将上一步产生的ORDERER_TRANSACTION转化为kafka类型的消息,之后通过enqueue()发送到kafka的响应topic中用以排序。
接下来,kafka会对收到的消息进行排序,排序完成后,进行实际的创建genesis块的工作,该部分是在processRegular()函数中实现的,该部分定义在orderer/consensus/kafka/chain.go中。其源码如下所示:
func (chain *chainImpl) processRegular(regularMessage *ab.KafkaMessageRegular, receivedOffset int64) error {
case ab.KafkaMessageRegular_CONFIG:
// Any messages coming in here may or may not have been re-validated
// and re-ordered, BUT they are definitely valid here
// advance lastOriginalOffsetProcessed iff message is re-validated and re-ordered
offset := regularMessage.OriginalOffset
if offset == 0 {
offset = chain.lastOriginalOffsetProcessed
}
commitConfigMsg(env, offset)
}
commitConfigMsg()函数就是实际写入block的函数,其代码如下所示:
// When committing a config message, we also update `lastOriginalOffsetProcessed` with `newOffset`.
// It is caller's responsibility to deduce correct value of `newOffset` based on following rules:
// - if Resubmission is switched off, it should always be zero
// - if the message is committed on first pass, meaning it's not re-validated and re-ordered, this value
// should be the same as current `lastOriginalOffsetProcessed`
// - if the message is re-validated and re-ordered, this value should be the `OriginalOffset` of that
// Kafka message, so that `lastOriginalOffsetProcessed` is advanced
commitConfigMsg := func(message *cb.Envelope, newOffset int64) {
logger.Debugf("[channel: %s] Received config message", chain.ChainID())
batch := chain.BlockCutter().Cut()
chain.lastOriginalOffsetProcessed = newOffset
block := chain.CreateNextBlock([]*cb.Envelope{message})
metadata := utils.MarshalOrPanic(&ab.KafkaMetadata{
LastOffsetPersisted: receivedOffset,
LastOriginalOffsetProcessed: chain.lastOriginalOffsetProcessed,
LastResubmittedConfigOffset: chain.lastResubmittedConfigOffset,
})
chain.WriteConfigBlock(block, metadata)
chain.lastCutBlockNumber++
chain.timer = nil
}
至此,一个channel的genesis区块也就创建完成了。
Hyperledger Fabric(v1.2.0)代码分析1——channel创建的更多相关文章
- Hyperledger Fabric(v1.1.0)编译时遇到的问题
Hyperledger Fabric(v1.1.0)编译时遇到的问题 0. 编译过程的坑 编译时,按照如下顺序编译 make release,编译源码生成二进制文件 make docker,生成一系列 ...
- 使用docker加载已有镜像安装Hyperledger Fabric v1.1.0
背景 每次在新的服务器上安装Hyperledger Fabric网络时,通过fabric官方提供的脚本安装时,需要从网络上down下近10G的fabric相关镜像,这个过程是漫长及痛苦的,有时因网络问 ...
- Hyperledger Fabric v1.1.0安装记录(国内源版)
1. 安装虚拟机 虚拟机软件采用:VirtualBox 操作系统选择:Ubuntu 14.04 内存:4G CPU:2核 硬盘:20G 2.(可选)更改 ...
- 阿里云容器服务区块链解决方案全新升级 支持Hyperledger Fabric v1.1
摘要: 全球开源区块链领域影响最为广泛的Hyperledger Fabric日前宣布了1.1版本的正式发布,带来了一系列丰富的新功能以及在安全性.性能与扩展性等方面的显著提升.阿里云容器服务区块链解决 ...
- 安装hyperledger fabric V1.0.1
安装文档位置: https://github.com/hyperledger/fabric fabric代码托管地址 https://hyperledger-fabric.readthedoc ...
- 安装hyperledger fabric V1.0.0-beta
安装文档位置: https://github.com/hyperledger/fabric fabric代码托管地址 https://hyperledger-fabric.readthedoc ...
- Ubuntu下搭建Hyperledger Fabric v1.0环境
多次尝试才正常启动了Fabric,如遇到各种莫名错误,请参考如下一步步严格安装,特别用户权限需要注意. 一.安装Ubuntu16 虚拟机或双系统,虚拟机有VirtualBox或者VMware,Ub ...
- 三、主流区块链技术特点及Hyperledger Fabric V1.0版本特点
一.Hyperledger fabric V1.0 架构 1.逻辑架构: 2.区块链网络 3.运行时架构 二.架构总结 1.架构要点 分拆Peer的功能,将Blockchain的数据维护和共识服务进行 ...
- 003-主流区块链技术特点及Hyperledger Fabric V1.0版本特点
一.Hyperledger fabric V1.0 架构 1.逻辑架构: 2.区块链网络 3.运行时架构 二.架构总结 1.架构要点 分拆Peer的功能,将Blockchain的数据维护和共识服务进行 ...
随机推荐
- Cobalt Strike 简单使用
1.运行服务端 其中afanti就是密码 2.客户端 用户名随意写,密码添afanti 3.创建listener 4.生成木马客户端 Attacks->Packages->Windows ...
- 机器学习中的特征缩放(feature scaling)
参考:https://blog.csdn.net/iterate7/article/details/78881562 在运用一些机器学习算法的时候不可避免地要对数据进行特征缩放(feature sca ...
- java 泛型数组列表
如下代码: package com.company; import java.lang.reflect.Array; import java.util.ArrayList; public class ...
- MySQL(五)SELECT语句执行顺序
上一篇讲述了Oracle的SELECT语法的执行顺序,这篇讲述MySQL的SELECT语法的执行顺序.MySQL的SELECT语法的执行顺序和Oracle的基本相同,只是增加了MySQL独有的LIMI ...
- 【题解】洛谷P1070 道路游戏(线性DP)
次元传送门:洛谷P1070 思路 一开始以为要用什么玄学优化 没想到O3就可以过了 我们只需要设f[i]为到时间i时的最多金币 需要倒着推回去 即当前值可以从某个点来 那么状态转移方程为: f[i]= ...
- RadioButtonFor值为false.默认选中的问题
(自己看了下.图片有点宽.显示的不全.可以右键新标签查看) 作为一个新手.今天又开始了mvc的学习之旅.然而学习过程中又遇到了一个奇妙的问题.... 一切按部就班到了这里.注册界面. 一眼看上去就不对 ...
- C++重载运算符练习--对people类重载“= =”运算符和“=”运算符
题目描述 对people类重载“= =”运算符和“=”运算符,“==”运算符判断两个people类对象的id属性是否相等:“=”运算符实现people类对象的赋值操作. 代码如下 #include&l ...
- Linux—echo命令
echo命令的功能是在屏幕上显示一段文字,起到一个提示作用,常用在脚本语言和批处理文件中来在标准输出或者文件中显示一行文本或者字符串. 命令格式:echo [选项] 字符串 选项参数: -n:不在最后 ...
- 小技巧textbox的行数
没什么技术含量,但如果不知道则实现起来很麻烦. c#中textbox.lines只记录回车的数量,并不是真正的总行数,如何得到呢,请使用: int 总行数 = this.textBox1.GetLin ...
- 20155211 Exp1 PC平台逆向破解(5)M
20155211 Exp1 PC平台逆向破解(5)M 实践目标 本次实践的对象是一个名为pwn1的linux可执行文件. 该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入 ...