背景
在fabric联盟链的同一个通道中,同时使用couchdb和leveldb的peer,在执行多节点背书或者查询时,sdk可能会报错mismatch
,两种数据库返回的同一key的查询结果居然不完全相同。
分析
起初以为是couchdb会自动修改json的字段顺序为字母顺序,当时还在心里大骂couchdb。自己写了一个客户端尝试写入kv到couchdb,查询的结果字段顺序并无变化,只能怀疑是fabric自己做了额外排序。
果不其然,fabric对json做了unmarshal。因为jsonMap是map[string]interface{}
,所以重新marshal时,按照字母顺序排序了。
// core/ledger/kvledger/txmgmt/statedb/statecouchdb/couchdoc_conv.go
func keyValToCouchDoc(kv *keyValue) (*couchDoc, error) {
type kvType int32
const (
kvTypeDelete = iota
kvTypeJSON
kvTypeAttachment
)
key, value, metadata, version := kv.key, kv.Value, kv.Metadata, kv.Version
jsonMap := make(jsonValue)
var kvtype kvType
switch {
case value == nil:
kvtype = kvTypeDelete
// check for the case where the jsonMap is nil, this will indicate
// a special case for the Unmarshal that results in a valid JSON returning nil
case json.Unmarshal(value, &jsonMap) == nil && jsonMap != nil:
kvtype = kvTypeJSON
if err := jsonMap.checkReservedFieldsNotPresent(); err != nil {
return nil, err
}
default:
// create an empty map, if the map is nil
if jsonMap == nil {
jsonMap = make(jsonValue)
}
kvtype = kvTypeAttachment
}
verAndMetadata, err := encodeVersionAndMetadata(version, metadata)
if err != nil {
return nil, err
}
// add the (version + metadata), id, revision, and delete marker (if needed)
jsonMap[versionField] = verAndMetadata
jsonMap[idField] = key
if kv.revision != "" {
jsonMap[revField] = kv.revision
}
if kvtype == kvTypeDelete {
jsonMap[deletedField] = true
}
jsonBytes, err := jsonMap.toBytes()
if err != nil {
return nil, err
}
couchDoc := &couchDoc{jsonValue: jsonBytes}
if kvtype == kvTypeAttachment {
attachment := &attachmentInfo{}
attachment.AttachmentBytes = value
attachment.ContentType = "application/octet-stream"
attachment.Name = binaryWrapper
attachments := append([]*attachmentInfo{}, attachment)
couchDoc.attachments = attachments
}
return couchDoc, nil
}
总结
不同语言marshal的json字段顺序也往往不一致,所以fabric网络不同组织如果混用不同语言实现的同一个业务合约,可能会遇到类似的麻烦,解决方案也很简单,可以在sdk层做一下json字段的重排。
目前截止fabric2.2.x这个最新的LTS版本都有这个问题,但是新版的fabric已经在修复此问题,详见