本教程將演示收集器(collection)的使用,收集器為區塊鏈網絡上已授權的組織節點 提供私有數據的存儲和檢索。
本教程假設您已了解私有數據的存儲和他們的用例。更多的信息請參閱 私有數據 。
本教程將帶你通過以下步驟練習在 Fabric 中定義、配置和使用私有數據:
- 創建一個收集器的 JSON 定義文件
- 使用鏈碼 API 讀寫私有數據
- 安裝和初始化帶有收集器的鏈碼
- 存儲私有數據
- 使用一個授權節點查詢私有數據
- 以授權節點的身份查詢私有數據
- 清除私有數據
- 使用私有數據索引
- 其他資源
本教程將使用 彈珠私有數據示例(marbles private data sample) --- 運行在“構建你的第一個網絡(BYFN)”教程的網絡上 --- 來演示創建、部署和使用私有數 據收集器。彈珠私有數據示例將部署在 構建你的第一個網絡 (BYFN)教程的網絡上。你 需要先完成 安裝示例、二進制文件和 Docker 鏡像 任務;但是在本教程中不需要運行 BYFN 教程。除了本教程中提 供的使用網絡所必須的命令,我們還會講解每一步都發生了什么,讓你不運行示例也可以理解 每一步的意義。
創建一個收集器的 JSON 定義文件
在通道中數據私有化的第一步是創建一個定義了私有數據權限的收集器。
收集器定義描述了誰可以持有數據、數據要分發到多少個節點上、多少節點可以傳播私有數據 和私有數據要在私有數據庫中存放多久。然后,我們將演示鏈碼 API PutPrivateData
和 GetPrivateData
是如何將收集器映射到受保護的私有數據的。
收集器的定義包括一下屬性:
name
: 收集器的名字。policy
: 定義了可以持有數據收集器的組織節點。requiredPeerCount
: 作為鏈碼的背書條件,需要將私有數據傳播到的節點數量。maxPeerCount
: 為了數據冗余,現有背書節點需要嘗試將數據分發到其他節點的數量。如 果背書節點發生故障,當有請求提取私有數據時,則其他節點在提交時可用。blockToLive
: 對於非常敏感的信息,比如價格或者個人信息,這個值表示在數據要以區塊 的形式在私有數據庫中存放的時間。數據將在私有數據庫中存在指定數量的區塊數然后會被清除, 也就是數據會從網絡中廢棄。要永久保存私有數據,永遠不被清除,就設置blockToLive
為0
。memberOnlyRead
: 值為true
則表示節點會自動強制只有屬於收集器成員組織的客戶端才 有讀取私有數據的權限。
為了說明私有數據的用法,彈珠私有數據示例包含了兩個私有數據收集器的定義: collectionMarbles
和 collectionMarblePrivateDetails
。在 collectionMarbles
中的 policy
屬性 定義了允許通道中(Org1 和 Org2)所有成員使用私有數據庫中的私有數據。 collectionMarblePrivateDetails
收集器只允許 Org1 的成員使用私有數據庫中的私有數據。
創建策略定義的更多信息請參考 背書策略 主題。
// collections_config.json [ { "name": "collectionMarbles", "policy": "OR('Org1MSP.member', 'Org2MSP.member')", "requiredPeerCount": 0, "maxPeerCount": 3, "blockToLive":1000000, "memberOnlyRead": true }, { "name": "collectionMarblePrivateDetails", "policy": "OR('Org1MSP.member')", "requiredPeerCount": 0, "maxPeerCount": 3, "blockToLive":3, "memberOnlyRead": true } ]
被這些策略保護的數據會被映射到鏈碼,教程的后邊會進行介紹。
當和它關聯的鏈碼在通道上參照 節點鏈碼初始化命令(peer chaincode instantiate command) 初始化以后,這個收集器定義文件會被部署到通道上。更多的細節會在下邊的三個部分講解。
使用鏈碼 API 讀寫私有數據
理解如何在通道上私有化數據的下一步工作是構建鏈碼的數據定義。彈珠私有數據示例根據數 據的使用權限將私有數據分成了兩個部分。
// Peers in Org1 and Org2 will have this private data in a side database
type marble struct { ObjectType string `json:"docType"` Name string `json:"name"` Color string `json:"color"` Size int `json:"size"` Owner string `json:"owner"` } // Only peers in Org1 will have this private data in a side database type marblePrivateDetails struct { ObjectType string `json:"docType"` Name string `json:"name"` Price int `json:"price"` }
私有數據的特定權限將會被限制為如下:
name, color, size, and owner
通道中所有成員可見(Org1 and Org2)price
只有 Org1 的成員可見
在彈珠私有數據示例中定義了兩個不同的私有數據收集器。數據映射到收集器策略(權 限限制)是通過鏈碼 API 控制的。特別地,使用收集器定義進行讀和寫私有數據是通過調用 GetPrivateData()
和 PutPrivateData()
來實現的,你可以在 這里 找到。
下邊的圖片闡明了彈珠私有數據示例所使用的私有數據模型。
讀取收集器數據
使用鏈碼 API GetPrivateData()
來查詢數據庫中的私有數據。 GetPrivateData()
需要兩個參數, 收集器名 和數據的鍵值。再說一下收集器 collectionMarbles
允許 Org1 和 Org2 的成員使用側數據庫中的私有數據,收集器 collectionMarblePrivateDetails
只允許 Org1 的成員使用側數據庫中的私有數據。詳情請參閱下邊的兩個 彈珠私有數據函數(marbles private data functions) :
- readMarble 用於查詢
name, color, size and owner
屬性的值- readMarblePrivateDetails 用於查詢
price
屬性的值
本教程后邊在節點上執行數據庫查詢的命令時,我們就是調用這兩個函數。
寫入私有數據
使用鏈碼 API PutPrivateData()
將私有數據存入私有數據庫。這個 API 同樣需要收集器的 名字。因為彈珠私有數據示例包含兩個不同的收集器,它在鏈碼中會被調用兩次:
- 使用名為
collectionMarbles
的收集器寫入私有數據name, color, size and owner
。 - 使用名為
collectionMarblePrivateDetails
的收集器寫入私有數據price
。
例如,在下邊的 initMarble
函數片段中, PutPrivateData()
被調用了兩次, 每個私有數據集合各一次。
// ==== Create marble object, marshal to JSON, and save to state ====
marble := &marble{ ObjectType: "marble", Name: marbleInput.Name, Color: marbleInput.Color, Size: marbleInput.Size, Owner: marbleInput.Owner, } marbleJSONasBytes, err := json.Marshal(marble) if err != nil { return shim.Error(err.Error()) } // === Save marble to state === err = stub.PutPrivateData("collectionMarbles", marbleInput.Name, marbleJSONasBytes) if err != nil { return shim.Error(err.Error()) } // ==== Create marble private details object with price, marshal to JSON, and save to state ==== marblePrivateDetails := &marblePrivateDetails{ ObjectType: "marblePrivateDetails", Name: marbleInput.Name, Price: marbleInput.Price, } marblePrivateDetailsBytes, err := json.Marshal(marblePrivateDetails) if err != nil { return shim.Error(err.Error()) } err = stub.PutPrivateData("collectionMarblePrivateDetails", marbleInput.Name, marblePrivateDetailsBytes) if err != nil { return shim.Error(err.Error()) }
總結一下,上邊我們為 collection.json
定義的策略允許 Org1 和 Org2 的所有 節點在他們的私有數據庫中存儲和交易彈珠的私有數據 name, color, size, owner
。 但是只有 Org1 的節點可以在他的私有數據庫中存儲和交易 price
私有數據。
數據私有的一個額外的好處是,當使用了收集器以后,只有私有數據的哈希會通過排序節點, 而不是私有數據本身,從排序方面保證了私有數據的機密性。
啟動網絡
現在我們准備通過一些命令來演示使用私有數據。
Try it yourself
在安裝和初始化彈珠私有數據鏈碼之前,我們需要啟動 BYFN 網絡。為了本教程,我們需要 在一個已知的初始化環境下操作。下邊的命令會關閉所有活動狀態的或者存在的 docker 容 器並刪除之前生成的構件。讓我們運行下邊的命令來清理之前的環境:
cd fabric-samples/first-network ./byfn.sh down
如果你之前運行過本教程,你需要刪除彈珠私有數據鏈碼的 docker 容器。讓我們運行下邊 的命令清理之前的環境:
docker rm -f $(docker ps -a | awk '($2 ~ /dev-peer.*.marblesp.*/) {print $1}') docker rmi -f $(docker images | awk '($1 ~ /dev-peer.*.marblesp.*/) {print $3}')運行下邊的命令來啟動使用了 CouchDB 的 BYFN 網絡:
./byfn.sh up -c mychannel -s couchdb
這會創建一個簡單的 Fabric 網絡,包含一個名為
mychannel
的通道,其中有兩個組織 (每個組織有兩個 peer 節點)和一個排序服務,同時使用 CouchDB 作為狀態數據庫。LevelDB 或者 CouchDB 都可以使用收集器。這里使用 CouchDB 來演示如何對私有數據進行索引。注解
為了讓收集器能夠工作,正確配置跨組織的 gossip 是很重要的。參考文檔 Gossip 數據傳播協議 , 重點關注 "錨節點" 部分。我們的教程不關注 gossip ,它已經在 BYFN 示例中配置過了, 但是當配置通道的時候,gossip 錨節點的配置對於收集器的正常工作是很重要的。
安裝和初始化帶有收集器的鏈碼
客戶端應用通過鏈碼和區塊鏈賬本交互。所以我們需要在每一個要執行和背書交易的節點 上安裝和初始化鏈碼。鏈碼安裝在節點上然后在通道上使用 peer-commands 進行初始化。
在所有節點上安裝鏈碼
就像上邊討論的,BYFN 網絡包含兩個組織, Org1 和 Org2 ,每個組織有兩個節點。所以 鏈碼需要安裝在四個節點上:
- peer0.org1.example.com
- peer1.org1.example.com
- peer0.org2.example.com
- peer1.org2.example.com
使用 peer chaincode install 命令在每一個節點上安裝彈珠鏈碼。
Try it yourself
如果你已經啟動了 BYFN 網絡,進入 CLI 容器。
docker exec -it cli bash
你的終端會變成類似這樣的:
root@81eac8493633:/opt/gopath/src/github.com/hyperledger/fabric/peer#
使用下邊的命令在 BYFN 網絡上,安裝 git 倉庫的彈珠鏈碼到節點
peer0.org1.example.com
(默認情況下,啟動 BYFN 網絡以后,激活的節點被設置成了CORE_PEER_ADDRESS=peer0.org1.example.com:7051
):peer chaincode install -n marblesp -v 1.0 -p github.com/chaincode/marbles02_private/go/
當完成之后,你會看到類似輸出:
install -> INFO 003 Installed remotely response:<status:200 payload:"OK" >
利用 CLI 切換當前節點為 Org1 的第二個節點並安裝鏈碼。復制和粘貼下邊的命令 到 CLI 容器並運行他們。
export CORE_PEER_ADDRESS=peer1.org1.example.com:8051 peer chaincode install -n marblesp -v 1.0 -p github.com/chaincode/marbles02_private/go/
利用 CLI 切換到 Org2 。復制和粘貼下邊的一組命令到節點容器並執行。
export CORE_PEER_LOCALMSPID=Org2MSP export PEER0_ORG2_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt export CORE_PEER_TLS_ROOTCERT_FILE=$PEER0_ORG2_CA export CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp切換當前節點為 Org2 的第一個節點並安裝鏈碼:
export CORE_PEER_ADDRESS=peer0.org2.example.com:9051 peer chaincode install -n marblesp -v 1.0 -p github.com/chaincode/marbles02_private/go/
切換當前節點為 Org2 的第二個節點並安裝鏈碼:
export CORE_PEER_ADDRESS=peer1.org2.example.com:10051 peer chaincode install -n marblesp -v 1.0 -p github.com/chaincode/marbles02_private/go/
在通道上初始化鏈碼
使用 peer chaincode instantiate 命令在通道上初始化彈珠鏈碼。為了在通道上配置鏈碼收集器,使用 --collections-config
標識來指定收集器的 JSON 文件,我們的示例中是 collections_config.json
。
Try it yourself
在 BYFN 的
mychannel
通道上運行下邊的命令來初始化彈珠私有數據鏈碼。export ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem peer chaincode instantiate -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C mychannel -n marblesp -v 1.0 -c '{"Args":["init"]}' -P "OR('Org1MSP.member','Org2MSP.member')" --collections-config $GOPATH/src/github.com/chaincode/marbles02_private/collections_config.json注解
當指定了
--collections-config
的時候,你需要指明 collections_config.json 文件完整清晰的路徑。 例如:--collections-config $GOPATH/src/github.com/chaincode/marbles02_private/collections_config.json
當成功初始化完成的時候,你可能看到類似下邊這些:
[chaincodeCmd] checkChaincodeCmdParams -> INFO 001 Using default escc [chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default vscc
存儲私有數據
以 Org1 成員的身份操作,Org1 的成員被授權可以交易彈珠私有數據示例中的所有私有數據,切換 回 Org1 的節點並提交一個增加一個彈珠的請求:
Try it yourself
復制並粘貼下邊的一組命令到 CLI 命令行。
export CORE_PEER_ADDRESS=peer0.org1.example.com:7051 export CORE_PEER_LOCALMSPID=Org1MSP export CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt export CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp export PEER0_ORG1_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
調用
initMarble
函數來創建一個帶有私有數據的彈珠 --- 名字為marble1
, 擁有者為tom
,顏色為blue
,尺寸為35
,價格為99
。重申一下,私 有數據 price 將會和私有數據 name, owner, color, size 分開存儲。因為這個原 因,initMarble
函數存儲私有數據的時候調用兩次PutPrivateData()
API ,每個 收集器一次。同樣要注意到,私有數據傳輸的時候使用了--transient
標識。為了保證 數據的隱私性,作為臨時數據傳遞的輸入不會保存在交易中。臨時數據以二進制的方式傳輸, 但是當使用 CLI 的時候,必須先進行 base64 編碼。我們使用一個環境變量來獲得 base64 編碼的值。 .. code:: bashexport MARBLE=$(echo -n "{\"name\":\"marble1\",\"color\":\"blue\",\"size\":35,\"owner\":\"tom\",\"price\":99}" | base64 | tr -d \n) peer chaincode invoke -o orderer.example.com:7050 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n marblesp -c '{"Args":["initMarble"]}' --transient "{\"marble\":\"$MARBLE\"}"你應該會看到類似下邊的結果:
[chaincodeCmd] chaincodeInvokeOrQuery->INFO 001 Chaincode invoke successful. result: status:200
清除私有數據
對於一些案例,私有數據僅需在賬本上保存到在鏈下數據庫復制之后就可以了,我們可以將 數據在過了一定數量的區塊后進行 “清除”,僅僅把數據的哈希作為不可篡改的證據保存下來。
私有數據可能會包含私人的或者機密的信息,比如我們例子中的價格數據,這是交易伙伴不想 讓通道中的其他組織知道的。但是,它具有有限的生命周期,就可以根據收集器定義中的,在 固定的區塊數量之后清除。
我們的 collectionMarblePrivateDetails
中定義 blockToLive
屬性的值為 3 , 表明這個數據會在側數據庫中保存三個區塊的時間,之后它就會被清除。將所有內容放在一 起,回想一下綁定了私有數據 price
的收集器 collectionMarblePrivateDetails
, 在函數 initMarble()
中,當調用 PutPrivateData()
API 並傳遞了參數 collectionMarblePrivateDetails
。
我們將從在鏈上增加區塊,然后來通過執行四筆新交易(創建一個新彈珠,然后轉移三個 彈珠)看一看價格信息被清除的過程,增加新交易的過程中會在鏈上增加四個新區塊。在 第四筆交易完成之后(第三個彈珠轉移后),我們將驗證一下價格數據是否被清除了。
Try it yourself
使用如下命令切換到 Org1 的 peer0 。復制和粘貼下邊的一組命令到節點容器並執行:
export CORE_PEER_ADDRESS=peer0.org1.example.com:7051 export CORE_PEER_LOCALMSPID=Org1MSP export CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt export CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp export PEER0_ORG1_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
打開一個新終端窗口,通過運行如下命令來查看這個節點上私有數據日志:
docker logs peer0.org1.example.com 2>&1 | grep -i -a -E 'private|pvt|privdata'
你將看到類似下邊的信息。注意列表中最高的區塊號。在下邊的例子中,最高的區塊高度是
4
。[pvtdatastorage] func1 -> INFO 023 Purger started: Purging expired private data till block number [0] [pvtdatastorage] func1 -> INFO 024 Purger finished [kvledger] CommitWithPvtData -> INFO 022 Channel [mychannel]: Committed block [0] with 1 transaction(s) [kvledger] CommitWithPvtData -> INFO 02e Channel [mychannel]: Committed block [1] with 1 transaction(s) [kvledger] CommitWithPvtData -> INFO 030 Channel [mychannel]: Committed block [2] with 1 transaction(s) [kvledger] CommitWithPvtData -> INFO 036 Channel [mychannel]: Committed block [3] with 1 transaction(s) [kvledger] CommitWithPvtData -> INFO 03e Channel [mychannel]: Committed block [4] with 1 transaction(s)
你將看到類似下邊的信息。注意列表中最高的區塊號。在下邊的例子中,最高的區塊高度是
4
。peer chaincode query -C mychannel -n marblesp -c '{"Args":["readMarblePrivateDetails","marble1"]}'
你將看到類似下邊的信息:
{"docType":"marblePrivateDetails","name":"marble1","price":99}
price
數據仍然在私有數據賬本上。通過執行如下命令創建一個新的 marble2 。這個交易將在鏈上創建一個新區塊。
export MARBLE=$(echo -n "{\"name\":\"marble2\",\"color\":\"blue\",\"size\":35,\"owner\":\"tom\",\"price\":99}" | base64 | tr -d \\n) peer chaincode invoke -o orderer.example.com:7050 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n marblesp -c '{"Args":["initMarble"]}' --transient "{\"marble\":\"$MARBLE\"}"再次切換回終端窗口並查看節點的私有數據日志。你將看到區塊高度增加了 1 。
docker logs peer0.org1.example.com 2>&1 | grep -i -a -E 'private|pvt|privdata'
返回到節點容器,再次運行如下命令查詢 marble1 的價格數據:
peer chaincode query -C mychannel -n marblesp -c '{"Args":["readMarblePrivateDetails","marble1"]}'
私有數據沒有被清除,之前的查詢也沒有改變查詢結果:
{"docType":"marblePrivateDetails","name":"marble1","price":99}
運行下邊的命令將 marble2 轉移給 “joe” 。這個交易將使鏈上增加第二個區塊。
export MARBLE_OWNER=$(echo -n "{\"name\":\"marble2\",\"owner\":\"joe\"}" | base64 | tr -d \\n) peer chaincode invoke -o orderer.example.com:7050 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n marblesp -c '{"Args":["transferMarble"]}' --transient "{\"marble_owner\":\"$MARBLE_OWNER\"}"再次切換回終端窗口並查看節點的私有數據日志。你將看到區塊高度增加了 1 。
docker logs peer0.org1.example.com 2>&1 | grep -i -a -E 'private|pvt|privdata'
返回到節點容器,再次運行如下命令查詢 marble1 的價格數據:
peer chaincode query -C mychannel -n marblesp -c '{"Args":["readMarblePrivateDetails","marble1"]}'
你將看到價格私有數據。
{"docType":"marblePrivateDetails","name":"marble1","price":99}
運行下邊的命令將 marble2 轉移給 “tom” 。這個交易將使鏈上增加第三個區塊。
export MARBLE_OWNER=$(echo -n "{\"name\":\"marble2\",\"owner\":\"tom\"}" | base64 | tr -d \\n) peer chaincode invoke -o orderer.example.com:7050 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n marblesp -c '{"Args":["transferMarble"]}' --transient "{\"marble_owner\":\"$MARBLE_OWNER\"}"再次切換回終端窗口並查看節點的私有數據日志。你將看到區塊高度增加了 1 。
docker logs peer0.org1.example.com 2>&1 | grep -i -a -E 'private|pvt|privdata'
返回到節點容器,再次運行如下命令查詢 marble1 的價格數據:
peer chaincode query -C mychannel -n marblesp -c '{"Args":["readMarblePrivateDetails","marble1"]}'
你將看到價格數據。
{"docType":"marblePrivateDetails","name":"marble1","price":99}
最后,運行下邊的命令將 marble2 轉移給 “jerry” 。這個交易將使鏈上增加第四個區塊。在 此次交易之后,
price
私有數據將會被清除。export MARBLE_OWNER=$(echo -n "{\"name\":\"marble2\",\"owner\":\"jerry\"}" | base64 | tr -d \\n) peer chaincode invoke -o orderer.example.com:7050 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n marblesp -c '{"Args":["transferMarble"]}' --transient "{\"marble_owner\":\"$MARBLE_OWNER\"}"再次切換回終端窗口並查看節點的私有數據日志。你將看到區塊高度增加了 1 。
docker logs peer0.org1.example.com 2>&1 | grep -i -a -E 'private|pvt|privdata'
返回到節點容器,再次運行如下命令查詢 marble1 的價格數據:
peer chaincode query -C mychannel -n marblesp -c '{"Args":["readMarblePrivateDetails","marble1"]}'
因為價格數據已經被清除了,你就查詢不到了。你應該會看到類似下邊的結果:
Error: endorsement failure during query. response: status:500 message:"{\"Error\":\"Marble private details does not exist: marble1\"}"
使用私有數據索引
索引也可以用於私有數據收集器,可以通過打包鏈碼旁邊的索引 META-INF/statedb/couchdb/collections/<collection_name>/indexes
來使用。有一個索引的例子在 這里 。
在生產環境下部署鏈碼時,建議和鏈碼一起定義索引,這樣當鏈碼在通道中的節點上安 裝和初始化時就可以自動作為一個單元進行安裝。當使用 --collections-config
標識 指定收集器 JSON 文件路徑時,通道上鏈碼初始化的時候相關的索引會自動被部署。
其他資源
這里有一個額外的私有數據學習的視頻。