Hyperledger Fabric——balance transfer(三)创建和加入Channel
详细解析blance transfer示例的创建通道(Channel)和加入节点到通道的过程。
创建Channel
1.首先看app.js的路由函数
var createChannel = require('./app/create-channel.js');
app.post('/channels', async function(req, res) {
// 接收参数channel名称和配置文件的路径
// 通道配置交易路径: ./artifacts/channel/mychannel.tx
var channelName = req.body.channelName;
var channelConfigPath = req.body.channelConfigPath;
if (!channelName) {
res.json(getErrorMessage('\'channelName\''));
return;
}
if (!channelConfigPath) {
res.json(getErrorMessage('\'channelConfigPath\''));
return;
}
// 创建通道(username和orgname参数从token中获取)
let message = await createChannel.createChannel(channelName, channelConfichannelNamegPath, req.username, req.orgname);
res.send(message);
});
2.再来看create-channel.js
中的createChannel()
方法
// 通过发送交易的方法向orderer节点发送创建channel的请求
var createChannel = async function(channelName, channelConfigPath, username, orgName) {
try {
// 生成组织的客户端实例
var client = await helper.getClientForOrg(orgName);
// 从channel配置交易(mychannel.tx)读取源字节
var envelope = fs.readFileSync(path.join(__dirname, channelConfigPath));
// 提取channel配置信息
var channelConfig = client.extractChannelConfig(envelope);
// 客户端使用 组织管理员 的私钥 签名,作为背书(endorsement)
// "../artifacts/channel/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/signcerts"
// 在内部通过_getSigningIdentity(true)获取org admin的私钥 进行签名
let signature = client.signChannelConfig(channelConfig);
// 请求结构:
let request = {
config: channelConfig,
signatures: [signature],
name: channelName,
txId: client.newTransactionID(true)
};
// 发送给orderer节点
var response = await client.createChannel(request)
if (response && response.status === 'SUCCESS') {
logger.debug('Successfully created the channel.');
let response = {
success: true,
message: 'Channel \'' + channelName + '\' created Successfully'
};
return response;
} else {
throw new Error('Failed to create the channel \'' + channelName + '\'');
}
} catch (err) {
logger.error('Failed to initialize the channel: ' + err.stack ? err.stack : err);
throw new Error('Failed to initialize the channel: ' + err.toString());
}
};
加入节点到Channel
1.首先看app.js里的路由函数
// 首先看app.js中的路由函数
app.post('/channels/:channelName/peers', async function(req, res) {
// 接收参数 channel名称(URL中)和peers(body中)
var channelName = req.params.channelName;
var peers = req.body.peers;
if (!channelName) {
res.json(getErrorMessage('\'channelName\''));
return;
}
if (!peers || peers.length == 0) {
res.json(getErrorMessage('\'peers\''));
return;
}
// 将peers加入channel
let message = await join.joinChannel(channelName, peers, req.username, req.orgname);
res.send(message);
});
2.然后看join-channel.js中的joinChannel()函数
// 然后看join-channel.js中的joinChannel()函数
var joinChannel = async function(channel_name, peers, username, org_name) {
var error_message = null;
var all_eventhubs = [];
try {
// 首先创建client对象
var client = await helper.getClientForOrg(org_name, username);
// 生成channel对象
var channel = client.getChannel(channel_name);
if(!channel) {
let message = util.format('Channel %s was not defined in the connection profile', channel_name);
logger.error(message);
throw new Error(message);
}
// 生成基于组织管理员的 TransactionID 对象
// 该对象包含一个随机数(nonce)以及t(nonce + signingIdentity)的hash值
let request = {
txId : client.newTransactionID(true)
};
// 从orderer节点获取创世区块(genesis block)
let genesis_block = await channel.getGenesisBlock(request);
// 告诉节点加入channel,等待每个节点的event hub通知我们节点加入channel成功
var promises = [];
var block_registration_numbers = [];
// 给组织中的每个节点分配一个EventHub对象
let event_hubs = client.getEventHubsForOrg(org_name);
#event_hubs.forEach((eh) => {
#let configBlockPromise = new Promise((resolve, reject) => {
// 设置超时时间
#let event_timeout = setTimeout(() => {
let message = 'REQUEST_TIMEOUT:' + eh._ep._endpoint.addr;
logger.error(message);
eh.disconnect();
reject(new Error(message));
}, 60000);
// 注册区块监听
#let block_registration_number = eh.registerBlockEvent((block) => {
clearTimeout(event_timeout);
// 配置区块中只能有一个交易
if (block.data.data.length === 1) {
// 一个节点可能属于多个channel
// 所以需要检验该区块是否来自目标channel
var channel_header = block.data.data[0].payload.header.channel_header;
if (channel_header.channel_id === channel_name) {
let message = util.format('EventHub %s has reported a block update for channel %s',eh._ep._endpoint.addr,channel_name);
logger.info(message)
resolve(message);
} else {
let message = util.format('Unknown channel block event received from %s',eh._ep._endpoint.addr);
logger.error(message);
reject(new Error(message));
}
}
}, (err) => {
clearTimeout(event_timeout);
let message = 'Problem setting up the event hub :'+ err.toString();
logger.error(message);
reject(new Error(message));
});
// 每一个client实例对应一个注册number,后面将作为参数对监听进行注销
block_registration_numbers.push(block_registration_number);
// 保存EventHub对象,方便后面对事件流断开连接
all_eventhubs.push(eh);
});
promises.push(configBlockPromise);
// 开启事件流
eh.connect();
});
// 封装加入channel的请求
let join_request = {
targets: peers, // 要加入channel的节点
txId: client.newTransactionID(true), // 基于组织管理员的TransactionID
block: genesis_block // 创世区块
};
// 调用SDK中的joinChannel()方法,主要是通过sendProposal()将
// 加入channel的交易提案发送给背书节点进行背书
let join_promise = channel.joinChannel(join_request);
// 保存返回结果:提案响应(ProposalResponse)的Promise
promises.push(join_promise);
let results = await Promise.all(promises);
logger.debug(util.format('Join Channel RESPONSE : %j', results));
// 检查所有Promise返回(包括监听事件和发送join请求)
// 只要有一个结果异常则宣布join channel失败
let peers_results = results.pop();
for(let i in peers_results) {
let peer_result = peers_results[i];
if(peer_result.response && peer_result.response.status == 200) {
logger.info('Successfully joined peer to the channel %s',channel_name);
} else {
let message = util.format('Failed to joined peer to the channel %s',channel_name);
error_message = message;
logger.error(message);
}
}
// 查看事件中心的消息报告
for(let i in results) {
let event_hub_result = results[i];
let event_hub = event_hubs[i];
let block_registration_number = block_registration_numbers[i];
logger.debug('Event results for event hub :%s',event_hub._ep._endpoint.addr);
if(typeof event_hub_result === 'string') {
logger.debug(event_hub_result);
} else {
if(!error_message) error_message = event_hub_result.toString();
logger.debug(event_hub_result.toString());
}
// 注销事件监听
event_hub.unregisterBlockEvent(block_registration_number);
}
} catch(error) {
logger.error('Failed to join channel due to error: ' + error.stack ? error.stack : error);
error_message = error.toString();
}
// 断开所有event hub事件流
all_eventhubs.forEach((eh) => {
eh.disconnect();
});
};
测试
创建channel
curl -s -X POST \
http://localhost:4000/channels \
-H "authorization: Bearer $ORG1_TOKEN" \
-H "content-type: application/json" \
-d '{
"channelName":"mychannel",
"channelConfigPath":"../artifacts/channel/mychannel.tx"
}'返回结果
{"success":true,"message":"Channel 'mychannel' created Successfully"}
将Org1中的两个peer加入channel
curl -s -X POST \
http://localhost:4000/channels/mychannel/peers \
-H "authorization: Bearer $ORG1_TOKEN" \
-H "content-type: application/json" \
-d '{
"peers": ["peer0.org1.example.com","peer1.org1.example.com"]
}'返回结果
{"success":true,"message":"Successfully joined peers in organization Org1 to the channel:mychannel"}
总结
创建channel:
- 生成
client
对象,从channel配置交易mychannel.tx
中提取配置信息 - 利用组织
admin
的私钥进行签名,这个过程相当于背书 - 封装请求结构(包括channel配置信息,对配置信息的签名,channelName,基于admin的txId), 并调用
clinet.createChannel()
将请求发送到orderer节点 - 实际上在内部调用
orderer.sendBroadcast()
,向order service
广播交易,orderer
将配置交易打包成区块后广播给各记账节点,记入账本中。(这个配置区块就是创建的channel所对应的chain的创世区块)
加入channel:
- 生成
client
对象,生成channel
对象 - 从
orderer
节点获取genesis block
(调用orderer.sendDeliver()
接口) - 对要加入
channel
的每个peer设置事件区块监听,根据指定channel是否生成了新的配置区块来判断join channel
过程是否成功 - 封装请求结构(包括 目标peers,基于组织admin的txId,genesis block),调用
channel.joinChannel()
方法,内部是通过peer.sendProposal()
将该交易提案发送给背书节点进行背书,返回背书响应Promise
客户端将背书过的交易发送到orderer服务,排序后广播给记账节点,将该配置区块记录到ledger中,此时EventHub
监听到该事件。 - 加入channel成功后,注销区块事件监听,断开所有
event hub
事件流。
Hyperledger Fabric——balance transfer(三)创建和加入Channel的更多相关文章
- Hyperledger Fabric——balance transfer(一)启动示例
Blacne transfer是Hyperledger fabric Node SDK的一个示例应用,主要使用了SDK中fabric-client 和 fabric-ca-client 模块中的API ...
- Hyperledger Fabric——balance transfer(六)查询
balance transfer 提供了很多查询接口,包括链码查询,根据区块号查询区块数据,根据交易ID查询交易信息,查询链上的区块数,查询已安装或已实例化的链码,查询通道. 源码解析 1.调用链码查 ...
- Hyperledger Fabric——balance transfer(四)安装和实例化chaincode
详细解析blance transfer示例的安装(install)和实例化(Instantiate)链码(chaincode)的过程.安装chaincode会根据本地的链码文件生成chaincode镜 ...
- Hyperledger Fabric——balance transfer(二)注册用户
详细分析blance transfer示例的用户注册(register)与登录(enroll)功能. 源码分析 1.首先分析项目根目录的app.js文件中关于用户注册和登录的路由函数.注意这里的tok ...
- Hyperledger Fabric——balance transfer(五)执行交易
链码安装和实例化之后就可以调用chaincode执行交易,下面分析简单的账户转账操作是如何完成的. 源码分析 1.首先看app.js的路由函数 app.post('/channels/:channel ...
- Hyperledger Fabric(v1.2.0)代码分析1——channel创建
Hyperledger Fabric(v1.2.0)代码分析1--channel创建 0. e2e_cli Hyperledger Fabric提供了一个e2e的例子,该例中创建了一个基础的区块链网络 ...
- HyperLedger Fabric Introduction——区块链超级账本介绍
介绍 HyperLedger Fabric是一个基于模块化架构的分布式账本解决方案平台,它拥有深度加密.便捷扩展.部署灵活及可插拔等特性.它设计之初的目的是支持不同组件的可插拔实现,并适应整个经济生态 ...
- Hyperledger Fabric介绍
转载地址 https://blog.csdn.net/xiaonu123/article/details/81006936 简介 Hyperledger介绍 超级账本(Hyperledger)项目是首 ...
- 搭建基于hyperledger fabric的联盟社区(三) --生成公私钥证书及配置文件
一.生成公私钥和证书 Fabric中有两种类型的公私钥和证书,一种是给节点之前通讯安全而准备的TLS证书,另一种是用户登录和权限控制的用户证书.这些证书本来应该是由CA来颁发,但是目前只有两个社区,所 ...
随机推荐
- 更加安全的密钥生成方法Diffie-Hellman
更加安全的密钥生成方法Diffie-Hellman 之前我们谈到了密钥配送的问题,这个世界是如此的危险, 一不小心通信线路就会被监听,那么我们怎么在这种不安全的线路中传递密钥呢? 这里我们介绍一下Di ...
- 【集群实战】fatab开机挂载失败案例
1. nfs挂载加入fstab案例 NFS客户端实现fstab开机自启动挂载 现象:nfs开机挂载卸载了/etc/fstab中,结果无法开机自动挂载nfs 解答:1. nfs客户端命令放在/etc/r ...
- Highcharts的自适应DOM或者DIV,JS方法实现
那我们就按照官网的一分钟极速入门代码来说 // 图表配置 var options = { chart: { type: 'bar' //指定图表的类型,默认是折线图(line) }, title: { ...
- Android 项目 Android 学习手册(一)
前言: 当每次查询android 知识的时候,内心是凌乱的,总觉得要是有一个工具多好, 尤其在手机端如何可以查询的话,会非常完美,能大大减少选择查询的时间, 之前见了很多java 学习手册,把一些重要 ...
- python(os 模块)
1.os.name 输出字符串指示正在使用的平台.如果是window 则用'nt'表示,对于Linux/Unix用户,它是'posix' import os print(os.name) #结果如下 ...
- postman(动态数据获取)
一:返回报文为 json 格式 示例:因为充值记录接口中需要用到登录接口返回报文中的信息如下 1.以获取token(JWT)和uid为例 2.在登录接口的tests中写入代码(因为登录接口报文信息中有 ...
- JavaSpring中级联查询
一对一级联查询映射文件PersonMapper.xml代码: <?xml version="1.0" encoding="UTF-8"?> < ...
- 模块_os模块
import os print(os.getcwd()) # 获取当前工作目录 print(os.listdir()) # 列表列出当前目录下的目录名和文件名 os.mkdir("tempd ...
- 一步一步教你PowerBI数据分析:制作客户RFM数据分析
客户分析就是根据客户信息数据来分析客户特征,评估客户价值,从而为客户制订相应的营销策略与资源配置.通过合理.系统的客户分析,企业可以知道不同的客户有着什么样的需求,分析客户消费特征与商务效益的关系,使 ...
- 12_JavaScript基础入门(2)
运算符 运算符(Operators,也翻译为操作符),是发起运算的最简单形式. 运算符的分类见仁见智,我们的课程对运算符进行如下分类: 数学运算符(Arithmetic operators) ...