问题
在一次服务迁移后,生产环境出现finger print mismatch
的问题
分析
调用合约时,peer会做如下检查
Invoke->CheckInvocation->ChaincodeEndorsementInfo
fabric1.x
func (lscc *SCC) ChaincodeEndorsementInfo(channelID, chaincodeName string, qe ledger.SimpleQueryExecutor) (*lifecycle.ChaincodeEndorsementInfo, error) {
chaincodeDataBytes, err := qe.GetState("lscc", chaincodeName)
if err != nil {
return nil, errors.Wrapf(err, "could not retrieve state for chaincode %s", chaincodeName)
}
...
err = ls.SecurityCheckLegacyChaincode(chaincodeData)
if err != nil {
return nil, errors.WithMessage(err, "failed security checks")
}
chaincodeDataBytes是从本地lscc中查询出来的,但是这个是initialized后记录的,是通道内共识的合约信息。
SecurityCheckLegacyChaincode将文件系统中记录的ccpack
和lscc记录的cd
做比较,如果不同则报错data mismatch
。
func (ls *LegacySecurity) SecurityCheckLegacyChaincode(cd *ccprovider.ChaincodeData) error {
...
ccpack, err := lscc.Support.GetChaincodeFromLocalStorage(cd.ChaincodeID())
...
if err = ccpack.ValidateCC(cd); err != nil {
return InvalidCCOnFSError(err.Error())
}
...
}
func (ccpack *CDSPackage) ValidateCC(ccdata *ChaincodeData) error {
...
if !ccpack.data.Equals(otherdata) {
return fmt.Errorf("data mismatch")
}
chaincode hash计算方法,包括CodeHash、MetaDataHash、SignatureHash。
func (ccpack *SignedCDSPackage) getCDSData(scds *pb.SignedChaincodeDeploymentSpec) ([]byte, []byte, *SignedCDSData, error) {
...
//get the code hash
hash.Write(cds.CodePackage)
scdsdata.CodeHash = hash.Sum(nil)
hash.Reset()
//get the metadata hash
hash.Write([]byte(cds.ChaincodeSpec.ChaincodeId.Name))
hash.Write([]byte(cds.ChaincodeSpec.ChaincodeId.Version))
scdsdata.MetaDataHash = hash.Sum(nil)
hash.Reset()
//get the signature hashes
if scds.InstantiationPolicy == nil {
return nil, nil, nil, fmt.Errorf("instantiation policy cannot be nil for chaincode (%s:%s)", cds.ChaincodeSpec.ChaincodeId.Name, cds.ChaincodeSpec.ChaincodeId.Version)
}
hash.Write(scds.InstantiationPolicy)
for _, o := range scds.OwnerEndorsements {
hash.Write(o.Endorser)
}
scdsdata.SignatureHash = hash.Sum(nil)
hash.Reset()
//compute the id
hash.Write(scdsdata.CodeHash)
hash.Write(scdsdata.MetaDataHash)
hash.Write(scdsdata.SignatureHash)
id = hash.Sum(nil)
...
}
//Equals data equals other
func (data *SignedCDSData) Equals(other *SignedCDSData) bool {
return other != nil &&
bytes.Equal(data.CodeHash, other.CodeHash) &&
bytes.Equal(data.MetaDataHash, other.MetaDataHash) &&
bytes.Equal(data.SignatureHash, other.SignatureHash)
}
fabric2.x
如果不是v20的通道,依然走lscc检查,否则不再做SecurityCheckLegacyChaincode。
func (cei *ChaincodeEndorsementInfoSource) ChaincodeEndorsementInfo(channelID, chaincodeName string, qe ledger.SimpleQueryExecutor) (*ChaincodeEndorsementInfo, error) {
...
if !ac.Capabilities().LifecycleV20() {
return cei.LegacyImpl.ChaincodeEndorsementInfo(channelID, chaincodeName, qe)
}
...
}
总结
首先,通过代码分析,我们知道fingerprint mismatch
是合约不同导致的,如果使用fabric2.x的非中心化合约,则不会校验合约hash是否相同,也就不会报这种错误。
在中心化生命周期的合约框架下,有两种可能导致codehash不同
- 一种就是不同peer安装的同名同版本合约其代码确实不同
- 另一种则是完全相同的代码去安装,结果codehash不同
fabric1.x的某些版本中的peer,还有go-sdk,有些会把文件的Mode也复制到tar.gz中(文件的修改时间等参数差异已经被开发组忽略),所以文件权限不同也会导致mismatch的问题。
func packEntry(tw *tar.Writer, gw *gzip.Writer, descriptor *Descriptor) error {
...
if stat, err := file.Stat(); err == nil {
...
header.Mode = int64(stat.Mode())
// Use a deterministic "zero-time" for all date fields
header.ModTime = time.Time{}
header.AccessTime = time.Time{}
header.ChangeTime = time.Time{}
// write the header to the tarball archive
if err := tw.WriteHeader(header); err != nil {
return err
}
文件存放的权限本身就可能不同,而且不同环境umask也可能不一样,所以即使代码相同,最后文件的mode也可能不一样,导致finggerprint mismatch的错误。
生产问题的原因,就是服务迁移后新环境的umask与之前不同,导致保存合约文件时mode与之前出现差异。