日志

Fabric进阶(三)—— 使用SDK动态增加组织

 来源    2020-05-23    0  

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

相关文章
Hyperledger Fabric 动态增加组织到网络中
日志本文基于Hyperledger Fabric 1.4版本. 官方文档地址:传送门 动态添加一个组织到Fabric网络中也是一个比较重要的功能.官方文档写的已经很详细了,有能力的尽量还是看官方文档,本文 ...
2
菜鸟系列Fabric——Fabric 动态添加组织(7)
日志Fabric 网络动态添加组织 1.环境准备 如果存在fabric网络环境可不执行,若不存在可以安装下列进行准备 下载fabric-sample,fabric https://github.com/h ...
1
如何动态增加jquery对话框的高度和宽度有一定的效果
问答当我显示我的对话框时,我的对话框高度为100,宽度为100 $("#dialog").dialog({ autoOpen: true, height: 100, width: 10 ...
1
ruby-on-rails – Ruby可以动态增加文件大小以进行测试
问答这可能听起来很奇怪,或者你为什么要那样做. 我正在尝试编写黄瓜功能来测试上传大图像文件(> 16M)所以,我不想将大文件存储在github或我的项目中.我正在使用imagemagick创建一个图 ...
2
区块链 – 在Hyperledger结构中动态添加组织中的组织或对等
问答我在Ubuntu VM中使用http://hyperledger-fabric-doc.readthedocs.io/en/latest/getting_started.html进行Hyperledg ...
2
javascript – 如何在角度ui树中动态增加父行
问答Plunker. 在这个我一直保持和.   用于递增父行并用于递增子行. 这里正确递增.并且"也正确递增, 如果我点击它然后它会为父母添加一个子行. 然后,如果我点击按钮,它应该为父母添加另 ...
动态增加/减少数组大小
问答我正在尝试动态增加数组的大小.是否有任何标准的C或C函数,它会在数组的末尾添加额外的空格或将其删除? 我知道,这很难,因为无法确保堆末端有足够的空间.但这不应该是操作系统的工作吗?::您正在寻找的功能 ...
1
如何动态增加按钮宽度取决于iPhone中的文字大小?
问答我以编程方式创建了10个按钮,并在按钮中设置了标题.现在我想动态增加按钮框架大小,它取决于文本. 我给出了一些条件并设置了帧大小.但是我如何设置确切的帧大小取决于文本(动态获取文本). 这是我的示例代 ...
1
javascript – 根据输入的字符动态增加输入类型文本文本框宽度
问答我有一个文本框,用户可以输入任意数量的字符,但我希望它的宽度相对于输入的字符数动态增加. 我已经做了一个下面显示的解决方法,它部分工作,它将动态增加宽度,但不会那么精确,并会在一段时间后隐藏第一个输入 ...
1
apache-spark – 如何动态增加在Yarn上运行的Spark中的活动任务
问答我正在运行一个火花流媒体流程,我收到了一批6000个活动.但是当我查看执行程序时,只有一个活动任务正在运行.我尝试了动态分配以及设置执行程序的数量等.即使我有15个执行程序,一次只运行一个活动任务.任 ...
2
动态增加Rcpp中列表的大小
问答我试图在Rcpp中实现"耦合到过去"的算法.为此,我需要存储一个随机数矩阵,如果算法没有收敛,则创建一个新的随机数矩阵并存储它.这可能需要做10次或者其他什么,直到收敛. 我希望我 ...
1
javascript – jQuery在for循环中动态增加变量名
问答是否有可能将i添加到for循环中的var? 在错误的语法中,它看起来像下面的代码 for(i=1; i<=countProjects; i++){ var test + i = $(otherV ...
1
动态增加java堆空间
问答我编写了一个java程序,用于测试具有不同处理器数量的不同机器上的几个多线程算法的速度. 在某些机器上,merge sort *失败,因为它需要一个相当大的堆空间来处理非常大的数组.在运行程序之前,我 ...
ios – 使用Fabric安装时,应用程序文件大小增加了2.5倍
问答我试图弄清楚为什么我的应用程序文件大小如此之大,当我安装使用Fabric Beta(由Twitter创建)的beta版本时. 归档构建并检查应用程序大小时,我发现它大约是194MB,但是,当我构建并安 ...
1
iphone – 如何检测哪个第三个sdk使用UDID?
问答我们使用来自广告提供商的大量第三方SDK,并且由于Apple在5月1日之后不允许进行UDID访问,因此我想检查哪些SDK使用UDID功能.有没有一种简单的方法或工具可以做到这一点?::一个简单而天真的 ...
1
asp-classic – 动态增加数组大小
问答我在ASP中有这个数组 CONST CARTPID = 0 CONST CARTPRICE = 1 CONST CARTPQUANTITY = 2 dim localCart(3,20) 我像这样动态 ...
1
c# – 在Windows窗体应用程序中动态增加/减少视频的速度
问答我正在VS 2010中构建一个Windows窗体应用程序,它可以根据用户输入的速度平滑地增加或减少视频播放的速度. 我试过几个途径.. 1.)使用AudioVideoPlayback DirectX类 ...
jasper-reports – 如何动态增加细节带宽
问答我的要求是当文本字段有更多的数据时动态增加细节带宽.有什么设置可以增加吗?我在细节带中使用一个textField,当它有更多的信息(单词),它只显示一些信息. 即这些词被切断.取决于单词显示的细节带宽 ...
1
iOS Swift – 动态增加UITextView和父UIView的高度
问答我几乎完全在键盘上方移动UITextView,根据文本量增加/减少其高度,然后将其返回到原始位置.但是,我无法让父UIView与UITextView一起成长. 如果我取消注释updateParentV ...
1
java – 如何在swing中动态增加JDialog大小?
问答我有一个JDialog,里面有一个JLabel,其中的内容是动态添加的.最初我设置了JDialog和JLabel的大小(两者都具有相同的大小),现在如果我在JLabel中的内容超过了JDialog的大 ...
1