fabric网络运行过程中动态追加新的组织是相当复杂的,网上的资料也十分匮乏,大多是基于first-network这样的简单示例,而且是使用启动cli容器的方法来增加组织,几乎没有针对实际应用的解决方案。本文介绍了如何在应用程序中调用SDK来进行组织的动态增加。

前言

首先需要介绍一个配置区块的概念,fabric中的配置信息是作为区块写在链上的,每个配置区块中只有一条配置交易,而且配置区块是全量更新的,最新的配置区块中应包含全部的配置信息。

回忆一下在创建通道时,会从本地读取通道配置交易(根据configtx.yaml生成),这个配置交易中指定了该通道中有哪些组织,以及设置了各组织的证书信息。如果想要在后续进行添加,就必须要让当前通道认可这个新组织,则需要提交一个包含新加组织的配置区块来对当前配置进行更新。

大致思路是首先从节点中获取到当前通道的最新配置区块,利用configtxlator工具将配置信息由protobuf格式转化为可读的json格式,手动在配置中添加上新组织的配置,然后再使用该工具计算修改前后的差值,将这个增量作为通道更新的请求发送出去。同时,这个通道更新的请求需要超过半数的当前组织签名才算有效。

调用SDK增加组织

因为是在fabric实际应用中增加组织,所以通过在app中编写代码调用SDK来完成所有操作是最优的方案。而且一旦实现,在之后的应用开发中可以很方便地复用,再配合上一些自动化脚本可以使繁杂的操作变得简单化,做到轻松的增加或删除网络内的组织。

值得一提的是,官方的node-sdk中提供了一段关于通道更新的例子configtxlator.js,不过里面实现的是删除某个组织,我们可以做一些改动来实现添加组织。

本文以balance-transfer v1.0为例,介绍如何通过调用Node SDK的方法,在已有两个组织的基础上增加新组织Org3,其中包含1个CA节点,2个Peer节点。

一、生成新组织证书目录

因为进入fabric网络是需要身份的,所以不论是加入新节点还是加入新组织,都要为新增的成员生成MSP目录。在artifacts/channel目录下创建新组织的配置文件org3-crypto.yaml

PeerOrgs:
- Name:Org3
Domain: org3.example.com
CA:
Hostname: ca
Template:
Count: 2
SANS:
- "localhost"
Users:
Count: 1

接着利用cryptogen工具生成Org3的msp目录,并输出到crypto-config目录中:

./cryptogen generate --config=./crypto-org2.yaml --output ./crypto-config

二、编写Nodejs代码调用SDK

我在app目录下创建了一个单独的文件add-org.js来完成添加组织,下面只提供程序的主要思路,细节可参考详细代码

1.安装所需Node模块

由于要在Nodejs程序中发送REST请求给configtxlator工具,所以需要事先安装模块(类似于curl):superagentsuperagent-promiserequest,其中request建议使用v1.9.8版本。导入模块:

var requester = require('request');
var agent = require('superagent-promise')(require('superagent'), Promise);

2.获取当前配置区块

调用getChannelConfig接口获取到最新的配置信息,接收到的结果是ConfigEvelope类型的对象:

var config_envelope = await channel.getChannelConfig()

我们只需要用到其中的config部分,取出后将其转化为二进制,注意original_config_proto是原始的配置信息,会在后面计算差值时用到。

var original_config_proto = config_envelope.config.toBuffer();

3.利用工具转化为json格式

使用configtxlator工具进行protobuf和json之间的转换,利用superagent-promise发出请求:

var response = await
agent.post('http://127.0.0.1:7059/protolator/decode/common.Config',original_config_proto).buffer();

对响应结果进行处理:

var original_config_json = response.text.toString()     // json string
var updated_config = JSON.parse(updated_config_json) // json object

4.手动增加新组织的信息

我们需要仿照已有的两个组织的配置结构添加上新组织的信息,首先复制Org1MSP部分的内容,注意这里通过先stringify再parse的方式完成一次深拷贝。

var new_config = JSON.parse(JSON.stringify(updated_config.channel_group.groups.Application.groups["Org1MSP"]));

接下来就是在new_config中做相关修改,主要包括两部分,一是所有跟组织名称有关的地方,都需要将Org1替换为Org3;二是将相关证书的值替换成Org3的MSP目录中的实际证书的内容(从文件中读取后还需要进行base64编码),三种证书的路径如下(当前位于app目录下,这里使用相对路径):

// 1.admins:组织管理员证书
'../artifacts/channel/crypto-config/peerOrganizations/org3.example.com/msp/admincerts/Admin@org3.example.com-cert.pem'
// 2.root_certs:根CA证书
'../artifacts/channel/crypto-config/peerOrganizations/org3.example.com/msp/admincerts/ca.org3.example.com-cert.pem'
// 3.tls_root_certs:tls根证书
'../artifacts/channel/crypto-config/peerOrganizations/org3.example.com/msp/admincerts/tlsca.org3.example.com-cert.pem'

需要修改的具体位置这里就不方便一一展开了,细节还是参考下代码。在编写js代码的时候可以将配置信息的json对象打印出来,对比下已有组织的配置内容,就可以很直观的找到那些需要替换的地方了。

完成编辑之后,将新组织的配置new_org当前到原有配置update_config上:

updated_config.channel_group.groups.Application.groups["Org3MSP"] = new_config;

并转化为json字符串:

updated_config_json = JSON.stringify(updated_config);

5.利用工具将json格式转为pb格式

response = await
agent.post('http://127.0.0.1:7059/protolator/encode/common.Config', updated_config_json.toString()).buffer();
var updated_config_proto = response.body; // 响应结果:pb格式

6.利用工具计算差值

通道更新请求需要的参数并不是新的配置信息,而是新配置与原始配置的一个差值,需要再次利用configtxlator工具计算这个增量。

首先构造向工具发送的请求体的结构,需要附带我们原始获取的配置original_config_proto以及修改过后的配置updated_config_proto,两者都是pb格式:

var formData = {
channel: channel_name,
original: {
value: original_config_proto,
options: {
filename: 'original.proto',
contentType: 'application/octet-stream'
}
},
updated: {
value: updated_config_proto,
options: {
filename: 'updated.proto',
contentType: 'application/octet-stream'
}
}
};

通过request模块发送post请求:

requester.post({
url:'http://127.0.0.1:7059/configtxlator/compute/update-from-configs',
encoding: null,
headers: {
accept: '/',
expect: '100-continue'
},
formData: formData
}

计算的结果转化为二进制以后赋值给变量config_proto,这就是通道配置的更新增量,下面会作为通道更新请求的重要参数。

7.对配置更新增量进行签名

更新通道的请求需要超过半数的已有组织的管理员身份签名,现有两个组织,则需要两个签名。调用help.js里的getOrgAdmin()方法可以给client对象分配管理员用户对象,然后调用SDK中的signChannelConfig()对配置进行签名:

var signatures = []
for (let org of cur_orgs) {
let client = helper.getClientForOrg(org)
await helper.getOrgAdmin(org) // 给client分配管理员对象
let signature = client.signChannelConfig(config_proto);
signatures.push(signature)
}

其中cur_orgs参数是除Orderer外所有组织名的集合,这里用了一个循环让所有组织管理员对配置签名。

8.发送更新通道的请求

首先构造请求体:

let tx_id = client.newTransactionID();
var request = {
config: config_proto, // 配置更新增量
signatures: signatures, // 组织管理员签名
name: channel_name,
orderer: channel.getOrderers()[0],
txId: tx_id
};

调用SDK的updateChannel()接口对通道进行更新,该方法在内部会将新的配置交易发送到orderer节点,打包成配置区块后分发给当前所有peer节点,peer节点将新的配置区块存入链中,此时该通道就接受认可了新加入的组织。

var result = await client.updateChannel(request);

三、执行代码加入新组织

Nodejs代码编写完成后整个工作就成功了一大半,接下来需要执行该程序,将Org3加入到当前网络。

首先启动configtxlator服务,默认监听7059端口:

configtxlator start

然后运行我们的Nodejs程序:

node add_org.js

成功响应后说明新组织加入成功,此时链上会生成一个新的配置区块。

四、更新配置文件

1.创建CA服务器配置文件

新加的组织Org3也拥有一个属于自己的CA节点,在之前的修改组织名的文章中已经介绍了如何设置CA服务器配置文件fabric-ca-server-config.yaml(主要是affiliations部分需要修改),以及如何在docker-compose文件中将该文件映射到CA容器内部。我的Github中也保存了该配置文件的模板

2.编写容器配置文件启动新组织节点

现在启动Org3中的节点,首先需要编写docker-compose文件。这一步比较简单,只要模仿已有组织的docker-compose.yaml文件即可。

Org3包含一个CA节点,两个Peer节点。编写该配置文件需要注意:如果所有组织都在一个机器上,则要保证容器的端口不会冲突。而且CA容器中的CA_KEYFILETLS_KEYFILE两个参数要和实际新组织的msp目录中的私钥文件路径一致。最后不要忘记添加CA服务器配置文件的映射。

将已完成Org3的配置文件docker-compose-org3.yaml置于artifacts目录下,执行以下命令启动三个节点:

docker-compose -f docker-compose-org3.yaml up -d

3.修改网络配置文件network-config.json

该文件路径为app/network-config.json,主要设置了网络各组织节点的ip和port信息,用于应用程序与网络节点进行交互。

需要仿照已有的组织,添加上新加入组织的信息,Org3部分大致如下:

"Org3": {
"name": "peerOrg3",
"mspid": "Org3MSP",
"ca": "https://localhost:7054",
"peers": {
"peer1": {
"requests": "grpcs://localhost:9051",
"events": "grpcs://localhost:9053",
"server-hostname": "peer0.org3.example.com",
"tls_cacerts": "../artifacts/channel/crypto-config/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/ca.crt"
},
"peer2": {
"requests": "grpcs://localhost:9056",
"events": "grpcs://localhost:9058",
"server-hostname": "peer1.org3.example.com",
"tls_cacerts": "../artifacts/channel/crypto-config/peerOrganizations/org3.example.com/peers/peer1.org3.example.com/tls/ca.crt"
}
},
"admin": {
"key": "../artifacts/channel/crypto-config/peerOrganizations/org3.example.com/users/Admin@org3.example.com/msp/keystore",
"cert": "../artifacts/channel/crypto-config/peerOrganizations/org3.example.com/users/Admin@org3.example.com/msp/signcerts"
}
}

五、将新组织中的节点加入通道

新组织的节点容器已经启动,首先需要在Org3注册某个用户,拿到Org3的TOKEN,这里设为ORG3_TOKEN,然后发送请求把Org3中的两个节点加入到通道中:

curl -s -X POST \
http://localhost:4000/channels/mychannel/peers \
-H "authorization: Bearer $ORG3_TOKEN" \
-H "content-type: application/json" \
-d '{
"peers": ["peer1","peer2"]
}'

六、升级链码

目前新组织节点没有安装链码,只能参与记账,无法指定其完成查询或交易操作。但是即使安装了旧版本的链码,会发现节点可以查询,但是进行的交易确是无效的。

这是因为在chaincode实例化的时候会指定背书策略,默认是channel其中一个组织的某一个成员进行背书,但是该背书策略中没有包含后续新加入的组织,所以在验证阶段会被标记成invalid,能一直产生区块,却不会写入状态数据库。

所以如果需要新加组织的节点来执行交易,则需要对链码进行升级,不改变链码内容,只改变版本和背书策略,为的就是在背书策略中加入新组织。

利用SDK来upgrade chaincode也并非易事,需要自行编写js代码来实现。升级链码和实例化链码很相似,都需要生成一个交易。SDK中提供了sendUpgradeProposal()方法来发送升级链码的提案,我们可以参考balance-transfer中的instantiateChaincode.js(链码实例化)代码来编写升级链码的代码,详细接口代码可见github

首先需要设置新的背书策略,该背书策略表示只要3个组织中的其中一个组织的任意一个节点对某个交易背书,该交易就满足策略。

var endorsement_policy =
{
identities: [
{ role: { name: "member", mspId: "Org1MSP" }},
{ role: { name: "member", mspId: "Org2MSP" }},
{ role: { name: "member", mspId: "Org3MSP" }}
],
policy: {
"1-of": [{ "signed-by": 0 }, { "signed-by": 1 }]
}
}

接下来构造升级链码的请求:

var request = {
"chaincodeId": "mycc",
"chaincodeVersion": "v1",
"args": [''],
"txId": client.newTransactionID(),
"endorsement-policy": endorsement_policy
};

然后发送提案到背书节点:

var results = channel.sendUpgradeProposal(request);

最后和交易流程一样,需要根据提案响应生成交易,发送到排序服务节点:

var sendPromise = channel.sendTransaction(txRequest);

成功后通道会接受新版本的链码,在Org3安装新链码后可以指定其节点进行有效的查询和交易操作。至此,添加新组织成功!

实际应用开发中的实现

应用开发中应该优先选择上述利用js脚本增加组织的方法。当然也可以使用cli容器的方法,最好要写一个脚本,自动启动cli容器,完成上述所有操作以后再删除cli容器,不过相比调用SDK还是有诸多不便。

我在实际开发中是将添加或删除组织升级链码这两个功能加入了应用程序代码中,写成了RESTful接口,客户端可以通过http请求来完成这两个操作。

并且还写了一个shell脚本,来自动化执行一些操作,包括生成证书,启动configtxlator工具,发送更改组织的请求,关闭工具等。如果进一步完善,甚至可以将后续修改配置文件等操作也加入脚本中,达到一键执行就能够完成增加或者删除组织的效果。

上述代码可以在我的Github中找到:https://github.com/zhayujie/fabric-tools

原文链接:https://zhayujie.com/add-org.html