如何解析chaincode package文件
背景
在做chaincode instanticate的时候碰到如下错误:
Error: Error endorsing query: rpc error: code = Unknown desc = error executing chaincode: could not get ChaincodeDeploymentSpec for {ccname}:{ccversion}: get ChaincodeDeploymentSpec for {ccname}/{channel} from LSCC error: chaincode fingerprint mismatch data mismatch - <nil>
原因是
首先peer在安装chaincode的时候会把chaincode安装到路径下面:/var/hyperledger/production/chaincodes/{ccname}.{ccversion},这是一个chaincode package格式文件,包含chaincode的打包源文件(tar.gz),和其他信息。
然后peer在第一次instanticate chaincode的时候会把chainocde的验证信息写到channel的state db里面,这样当chaincode死掉再重新instantiate的时候,会验证chaincode的package是否已经发生过变化,对照从chaincode package中读取的hash值是否和state db里面已经存储的hash值是否一致。
$ cat fabric/core/common/ccprovider/cdspackage.go
// ValidateCC returns error if the chaincode is not found or if its not a
// ChaincodeDeploymentSpec
func (ccpack *CDSPackage) ValidateCC(ccdata *ChaincodeData) error {
if ccpack.depSpec == nil {
return fmt.Errorf("uninitialized package")
}
if ccpack.data == nil {
return fmt.Errorf("nil data")
}
if ccdata.Name != ccpack.depSpec.ChaincodeSpec.ChaincodeId.Name || ccdata.Version != ccpack.depSpec.ChaincodeSpec.ChaincodeId.Version {
return fmt.Errorf("invalid chaincode data %v (%v)", ccdata, ccpack.depSpec.ChaincodeSpec.ChaincodeId)
}
otherdata := &CDSData{}
err := proto.Unmarshal(ccdata.Data, otherdata)
if err != nil {
return err
}
// Here verify the hash value whether same ?
if !ccpack.data.Equals(otherdata) {
return fmt.Errorf("data mismatch")
}
return nil
}
如果不一致,chaincode instantiate就会失败,报上述错误。
下面程序就是读取chaincode package的内容把其中的chaincode代码包,和hash值取出来。
package main
import (
"fmt"
"io/ioutil"
pb "github.com/hyperledger/fabric/protos/peer"
"github.com/hyperledger/fabric/core/common/ccprovider"
"github.com/golang/protobuf/proto"
)
/**
* This tool will extrace chaincode tar.gz package from chaincode CDSPackage
* Chaincode package are named format as: ccname.ccversion, and
* placed under path set by SetChaincodesPath
* Output:
* chaincode tar.gz file
* chaincode code and meta hash value
*/
func main() {
ccprovider.SetChaincodesPath("/var/hyperledger/production/chaincodes")
var ccpack *ccprovider.CDSPackage = &ccprovider.CDSPackage{}
var cdsdata *pb.ChaincodeDeploymentSpec
_, cdsdata, err := ccpack.InitFromFS("{ccname}", "{ccversion}")
if err != nil {
fmt.Printf("ERROR: Cannot load chaincode package, error=[%v]\n", err)
return
}
var ccdata *ccprovider.ChaincodeData = ccpack.GetChaincodeData()
otherdata := &ccprovider.CDSData{}
err = proto.Unmarshal(ccdata.Data, otherdata)
if err != nil {
fmt.Printf("ERROR: Cannot unmarshal chaincode data, error=[%v]\n", err)
return
}
ioutil.WriteFile("chaincode.tar.gz", cdsdata.CodePackage, 0644)
ioutil.WriteFile("chaincode.code.hash", []byte(fmt.Sprintf("%v\n", otherdata.CodeHash)), 0644)
ioutil.WriteFile("chaincode.meta.hash", []byte(fmt.Sprintf("%v\n", otherdata.MetaDataHash)), 0644)
fmt.Printf("Done\n")
}
我们可以解开chaincode.tar.gz查看chaincode文件的内容,注意chaincode.tar.gz的文件格式,是一个tar文件,也就是说包含原始代码文件的meta信息,比如读写权限,owner/group,时间戳等等等信息(好在fabric在打包的时候重写了其中的很多项目,例如把时间戳全部清零了,把owner/group也清零了,etc),只要有任何一个域值不一致,整个tar.gz文件就不一致,进而生成的hash值就不一致。
详细tar文件格式请参与:
https://www.gnu.org/software/tar/manual/html_node/Standard.html
另外注意不同的fabric版本,不同的go语言版本(tar库版本),甚至不同的安装方式(SDK,peer命令行安装)都可能导致产生的chaincode package不一样。