1. 程式人生 > >Fabric進階(三)—— 使用SDK動態增加組織

Fabric進階(三)—— 使用SDK動態增加組織

在**fabric**網路執行過程中動態追加新的組織是相當複雜的,網上的資料也十分匱乏,大多是基於**first-network**這樣的簡單示例,而且是使用啟動**cli**容器的方法來增加組織,幾乎沒有針對實際應用的解決方案。本文介紹瞭如何在應用程式中呼叫**SDK**來進行組織的動態增加。 ## 前言 首先需要介紹一個**配置區塊**的概念,fabric中的配置資訊是作為區塊寫在鏈上的,每個配置區塊中只有一條配置交易,而且配置區塊是全量更新的,最新的配置區塊中應包含全部的配置資訊。 回憶一下在建立通道時,會從本地讀取通道配置交易(根據`configtx.yaml`生成),這個配置交易中指定了該通道中有哪些組織,以及設定了各組織的證書資訊。如果想要在後續進行新增,就必須要讓當前通道認可這個新組織,則需要提交一個包含新加組織的配置區塊來對當前配置進行更新。 大致思路是首先從節點中獲取到當前通道的最新配置區塊,利用`configtxlator`工具將配置資訊由`protobuf`格式轉化為可讀的json格式,手動在配置中新增上新組織的配置,然後再使用該工具計算修改前後的差值,將這個增量作為通道更新的請求傳送出去。同時,這個通道更新的請求需要超過半數的當前組織簽名才算有效。 ## 呼叫SDK增加組織 因為是在fabric實際應用中增加組織,所以通過在app中編寫程式碼呼叫SDK來完成所有操作是最優的方案。而且一旦實現,在之後的應用開發中可以很方便地複用,再配合上一些自動化指令碼可以使繁雜的操作變得簡單化,做到輕鬆的增加或刪除網路內的組織。 值得一提的是,官方的node-sdk中提供了一段關於通道更新的例子[configtxlator.js][1],不過裡面實現的是刪除某個組織,我們可以做一些改動來實現新增組織。 本文以`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`來完成新增組織,下面只提供程式的主要思路,細節可參考[詳細程式碼][2]。 **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/[email protected]' // 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' ``` 需要修改的具體位置這裡就不方便一一展開了,細節還是參考下[程式碼][3]。在編寫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節點,在之前的[修改組織名][4]的文章中已經介紹瞭如何設定CA伺服器配置檔案`fabric-ca-server-config.yaml`(主要是affiliations部分需要修改),以及如何在docker-compose檔案中將該檔案對映到CA容器內部。我的Github中也儲存了該配置檔案的[模板][5]。 **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/[email protected]/msp/keystore", "cert": "../artifacts/channel/crypto-config/peerOrganizations/org3.example.com/users/[email protected]/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][6]。 首先需要設定新的背書策略,該背書策略表示只要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指令碼][7],來自動化執行一些操作,包括生成證書,啟動configtxlator工具,傳送更改組織的請求,關閉工具等。如果進一步完善,甚至可以將後續修改配置檔案等操作也加入指令碼中,達到一鍵執行就能夠完成增加或者刪除組織的效果。 上述程式碼可以在我的Github中