在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):superagent
,superagent-promise
和request
,其中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_KEYFILE
和TLS_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