Chaincode是一段由Go語言編寫(支持其他編程語言,如Java),並能實現預定義接口的程序。
Chaincode運行在一個受保護的Docker容器當中,與背書節點的運行互相隔離。
Chaincode可通過應用提交的交易對賬本狀態初始化並進行管理。
一段chaincode通常處理由網絡中的成員一致認可的業務邏輯,故我們很可能用“智能合約”來代指chaincode。
一段chiancode創建的(賬本)狀態是與其他chaincode互相隔離的,故而不能被其他chaincode直接訪問。
不過,如果是在相同的網絡中,一段chiancode在獲取相應許可後則可以調用其他chiancode來訪問它的賬本。
可參考:https://hyperledgercn.github.io/hyperledgerDocs/chaincode_operators_zh/
Chaincode 開發手冊
Chaincode API
Init
Init
(初始化)方法會在chaincode接收到instantiate
(實例化)或者upgrade
(升級)交易時被調用,
進而使得chaincode順利執行必要的初始化操作,包括初始化應用的狀態;
例子:
// Init is called during chaincode instantiation to initialize any
// data. Note that chaincode upgrade also calls this function to reset
// or to migrate data, so be careful to avoid a scenario where you
// inadvertently clobber your ledger’s data!
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
// Get the args from the transaction proposal
args := stub.GetStringArgs()
if len(args) != 2 {
return shim.Error("Incorrect arguments. Expecting a key and a value")
}
// Set up any variables or assets here by calling stub.PutState()
// We store the key and the value on the ledger
err := stub.PutState(args[0], []byte(args[1]))
if err != nil {
return shim.Error(fmt.Sprintf("Failed to create asset: %s", args[0]))
}
return shim.Success(nil)
}
Invoke
Invoke
(調用)方法會在響應invoke
(調用)交易時被調用以執行交易。
例子:
// Invoke is called per transaction on the chaincode. Each transaction is
// either a 'get' or a 'set' on the asset created by Init function. The Set
// method may create a new asset by specifying a new key-value pair.
func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
// Extract the function and args from the transaction proposal
fn, args := stub.GetFunctionAndParameters()
var result string
var err error
if fn == "set" {
result, err = set(stub, args)
} else { // assume 'get' even if fn is nil
result, err = get(stub, args)
}
if err != nil {
return shim.Error(err.Error())
}
// Return the result as success payload
return shim.Success([]byte(result))
}
// Set stores the asset (both key and value) on the ledger. If the key exists,
// it will override the value with the new one
func set(stub shim.ChaincodeStubInterface, args []string) (string, error) {
if len(args) != 2 {
return "", fmt.Errorf("Incorrect arguments. Expecting a key and a value")
}
err := stub.PutState(args[0], []byte(args[1]))
if err != nil {
return "", fmt.Errorf("Failed to set asset: %s", args[0])
}
return args[1], nil
}
// Get returns the value of the specified asset key
func get(stub shim.ChaincodeStubInterface, args []string) (string, error) {
if len(args) != 1 {
return "", fmt.Errorf("Incorrect arguments. Expecting a key")
}
value, err := stub.GetState(args[0])
if err != nil {
return "", fmt.Errorf("Failed to get asset: %s with error: %s", args[0], err)
}
if value == nil {
return "", fmt.Errorf("Asset not found: %s", args[0])
}
return string(value), nil
}
chaincode的依賴
chaincode引入必要的依賴,chaincode shim package和peer protobuf package。
例子:
package main
import (
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/protos/peer"
)
main函數
在main函數中通過API shim.start()來向特定peer結點註冊該chaincode
例子:
// main function starts up the chaincode in the container during instantiate
func main() {
if err := shim.Start(new(SimpleAsset)); err != nil {
fmt.Printf("Error starting SimpleAsset chaincode: %s", err)
}
}
ChaincodeStub提供的API
第一大類與state操作相關。
通過這些API可以根據key來查詢/添加/更新相應的state。
這些API提供了單key的讀寫操作、key字典序範圍讀取操作、composite key讀取操作、底層數據庫語法的查詢操作等。
第二大類與與參數相關。
fabric1.0修改了chaincode接口的定義,需要開發者自己調用API獲取傳入的參數。
注意,傳入的參數第一個是函數名稱,之後纔是相應的函數輸入參數。
第三大類與Transaction有關。
並且這一類都是讀操作,讀取transaction中各種信息,比如transaction id、timestamp等。
第四類是與chaincode間相互調用有關的一個API。
Fabric允許在一個chaincode中根據chaincode name和channel name去調用另一個chaincode。
可以看到並沒有deploy的API,也就是說,fabric不允許在一個chaincode中去部署新的chaincode。
第五類也只有一個API,SetEvent。
Fabric允許開發者定義自己的event,然後這個event會在transaction寫進block時觸發,
因此開發者就可以自己寫相應的event handler程序做一些相關的工作。
例子:
shim.ChaincodeStubInterface.GetStringArgs:獲取參數
shim.ChaincodeStubInterface.PutState:寫入賬本數據
shim.ChaincodeStubInterface.GetState:讀取賬本數據
shim.ChaincodeStubInterface.DelState:刪除賬本數據
Chaincode 操作手冊
創建包
打包chaincode有兩種方式。
第一種是當你想要讓chaincode有多個所有者的時候,此時就需要讓chaincode包被多個所有者簽名。
這種情況下需要我們創建一個被簽名的chaincode包(SignedCDS
),這個包依次被每個所有者簽名。
另一種就比較簡單了,這是當你要建立只有一個節點的簽名的時候(該節點執行install
交易)。
如果您對多用戶的情況不感興趣,您可以直接跳到後面的安裝chaincode部分。
要創建一個簽名過的chaincode包,請用下面的指令:
peer chaincode package -n mycc -p github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02 -v 0 -s -S -i "AND('OrgA.admin')" ccpack.out
-s
選項創建了一個可被多個所有者簽名的包,而非簡單地創建一個CDS。
如果使用-s
,那麼當其他所有者要簽名的時候,-S
也必須同時使用。
否則,該過程將創建一個僅包含實例化策略的簽名chaincode包(SignedCDS)。
-S
選項可以使在core.yaml
文件中被localMspid
相關屬性值定義好的MSP對包進行簽名。
-S
選項是可選的。不過,如果我們創建了一個沒有簽名的包,
那麼它就不能被任何其他所有者用signpackage
指令進行簽名。
-i
選項也是可選的,它允許我們爲chaincode指定實例化策略。
實例化策略與背書策略格式相同,它指明誰可以實例化chaincode。
在上面的例子中,只有OrgA的管理員纔有資格實例化chaincode。
如果沒有提供任何策略,那麼系統會採用默認策略,
該策略只允許peer節點MSP的管理員去實例化chaincode。
包的簽名
一個在創建時就被簽名的chaincode包可以交給其他所有者進行檢查與簽名。
具體的工作流程支持帶外對chaincode包簽名。
每個(chaincode的)所有者通過將ChaincodeDeploymentSpec與其本人的身份信息(證書)結合並對組合結果簽名來認證ChaincodeDeploymentSpec。
一個chaincode所有者可以對一個之前創建好的帶簽名的包進行簽名,具體使用如下指令:
peer chaincode signpackage ccpack.out signedccpack.out
指令中的ccpack.out
和signedccpack.out
分別是輸入與輸出包。
signedccpack.out
則包含一個用本地MSP對包進行的附加簽名。
安裝chaincode
install
交易的過程會將chaincode的源碼以一種被稱爲ChaincodeDeploymentSpec
(CDS)的規定格式打包,
並把它安裝在一個將要運行該chaincode的peer節點上。
注意:
請務必在一條channel上每一個要運行你chaincode的背書節點上安裝你的chaincode
如果只是簡單地給install
API一個ChaincodeDeploymentSpec
,它將使用默認實例化策略並添加一個空的所有者列表。
注意:
Chaincode應該僅僅被安裝於chaincode所有者的背書節點上,以使該chaincode邏輯對整個網絡的其他成員保密。
其他沒有chaincode的成員將無權成爲chaincode影響下的交易的認證節點(endorser)。
也就是說,他們不能執行chaincode。不過,他們仍可以驗證交易並提交到賬本上。
下面安裝chaincode。此時會發送一條 SignedProposal 到生命週期系統chaincode
(LSCC),該chaincode在系統chaincode部分會仔細描述。
舉個例子,使用CLI安裝簡單的賬本管理chaincode章節的sacc chaincode樣例時,命令如下:
peer chaincode install -n asset_mgmt -v 1.0 -p sacc
在CLI內部會爲sacc創建SignedChaincodeDeploymentSpec,並將其發送到本地peer節點。
這些節點會調用LSCC上的Install
方法。上述的-p
選項指明chaincode的路徑,
其必須在用戶的GOPATH
目錄下(比如$GOPATH/src/sacc
)。完整的命令選項詳見CLI部分。
注意:爲了在peer節點上安裝(chaincode),SignedProposal的簽名必須來自peer節點本地MSP的管理員中的一位。
實例化chaincode
實例化交易會調用生命週期系統chaincode
(LSCC)來在一個channel上創建並初始化一段chaincode。
下面是一個chaincode-channel綁定的具體過程:
一段chaincode可能會與任意數量的channel綁定並在每個channel上獨立運行。
換句話說,chaincode在多少個channel上安裝並實例化並沒有什麼影響,對於每個提交交易的channel,其狀態都是獨立而互不影響的。
一個實例化
交易的創建者必須符合在SignedCDS中chaincode的實例化策略,
且必須充當channel的寫入器(這會成爲channel創建配置的一部分)。
這對於channel的安全至關重要,因爲這樣可以防止惡意實體在未綁定的channel上部署chaincode,也能防止間諜成員在未綁定的channel上執行chaincode。
舉個例子,我們提到過默認的實例化策略是任何channel MSP的管理員(可以執行),
所以chaincode創建者要實例化交易,其本人必須是channel管理員的一員。
當交易提議到達背書成員時,它會驗證創建者的簽名是否符合實例化策略。
在交易被提交到賬本之前的交易驗證階段,以上操作還會再來一遍。
實例化交易的過程還會爲channel上的chaincode建立背書策略。背書策略描述了交易的相關認證要求,以使得交易能被channel中的成員認可。
例如,使用CLI去實例化上一章的sacc chaincode並初始化john
的狀態爲0
,指令具體如下:
peer chaincode instantiate -n sacc -v 1.0 -c '{"Args":["john","0"]}' -P "OR ('Org1.member','Org2.member')"
注意:
注意,上述背書策略(CLI使用波蘭表示法)向Org1或Org2的成員詢問所有sacc處理的交易。也就是說,爲確保交易有效,Org1或Org2必須爲調用sacc的結果簽名。
在成功實例化後,channel上的chaincode就進入激活狀態,並時刻準備執行任何ENDORSER_TRANSACTION類型的交易提議。交易會在到達背書節點的同時被處理。
升級chaincode
一段chaincode可以通過更改它的版本(SignedCDS的一部分)來隨時進行更新。
至於SignedCDS的其他部分,比如所有者及實例化策略,都是可選的。
不過,chaincode的名稱必須一致,否則它會被當做完全不同的另一段chaincode。
在升級之前,chaincode的新版本必須安裝在需要它的背書節點上。
升級是一個類似於實例化交易的交易,它會將新版本的chaincode與channel綁定。其他與舊版本綁定的channel則仍舊運行舊版本的chaincode。換句話說,升級
交易只會一次影響一個提交它的channel。
注意:
注意:由於多個版本的chaincode可能同時運行,所以升級過程不會自動移除舊版本,用戶必須親自處理。
升級
交易與實例化
交易有一處微妙的區別:升級
交易採用當前的chaincode實例化策略進行檢查,而非比對新的策略(如果指定了的話)。這是爲了確保只有當前實例化策略指定的已有成員才能升級chaincode。
注意:
注意:在升級過程中,chaincode的Init
函數會被調用以執行數據相關的操作,或者重新初始化數據;所以要多加小心避免在升級chaincode時重設狀態信息。
1 從宿主機將chaincode拷貝進容器
docker cp community2.0 cli:/opt/gopath/src/github.com/hyperledger/fabric/examples/chaincode/go
2 安裝新版本chaincode,打包到peer節點
docker exec -it cli bash
peer chaincode install -n mycc -v 2.0 -p github.com/hyperledger/fabric/examples/chaincode/go/community2.0
3 升級chaincode
peer chaincode upgrade -o orderer.example.com:7050 --tls true --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 mycc -v 2.0 -c '{"Args":["init"]}' -P "OR ('Org1MSP.member','Org2MSP.member')"