表象:Return schema invalid. required items must be unique [recovered]
雖然 Fabric v2.2 已經發布了很久了,但之前因為專案歷史問題,一直使用的都是 Fabric v1.4.8,所以智慧合約也一直使用的都是 github.com/hyperledger/fabric/core/chaincode/shim
包。
在合約開發過程中,我一般都是使用下面的介面格式來定義合約的業務邏輯:
func create(stub shim.ChaincodeStubInterface, payload string) ([]byte, error)
在開發 Fabric v2.2 的智慧合約時, 使用 github.com/hyperledger/fabric-contract-api-go/contractapi
替換 github.com/hyperledger/fabric/core/chaincode/shim
,介面格式如下:
func create(ctx contractapi.TransactionContextInterface, payload string) ([]byte, error)
然而這樣的介面在合約示例化的時候翻車了:
Error compiling schema for SmartContract [create]. Return schema invalid. required items must be unique [recovered]
翻閱 github.com/hyperledger/fabric-contract-api-go
時,在其使用教程發現一些限制:
- 合同的函式只能接受以下型別的引數:
- string
- bool
- int(包括 int8、int16、int32 和 int64)
- uint(包括 uint8、uint16、uint32 和 uint64)
- float32
- float64
- time.Time
- 任何允許型別的陣列/切片
- 結構體(其公共欄位全部屬於允許型別或另一個結構體)
- 指向結構體的指標
- 具有鍵型別為 string 和值為任何允許型別的對映
- interface{}(僅當直接傳入時才允許,在透過事務呼叫時將接收一個 string 型別)
- 合同的函式還可以接受事務上下文,前提是:
- 它作為第一個引數傳入
- 二選一:
- 它要麼是型別為 *contractapi.TransactionContext 的物件,要麼是在鏈碼中定義的自定義事務上下文,用於合同的使用
- 它是一個介面,用於合同的事務上下文型別符合該介面,例如 contractapi.TransactionContextInterface。
- 合同的函式只能返回零、一個或兩個值:
- 如果函式被定義為返回零值,那麼對該合同函式的所有呼叫將返回成功響應
- 如果函式被定義為返回一個值,那麼該值可以是引數列表中列出的任何允許型別之一(除了 interface{}),或者是錯誤。
- 如果函式被定義為返回兩個值,那麼第一個值可以是引數列表中列出的任何允許型別之一(除了 interface{}),第二個值必須是錯誤。
仔細閱讀會發現 func create(ctx contractapi.TransactionContextInterface, payload string) ([]byte, error)
並沒有違法上面的規則,但示例化的時候就是無法透過。
上面的報錯資訊也明確說了是返回值不對,那就改下介面的返回值:
func create(ctx contractapi.TransactionContextInterface, payload string) (string, error)
func create(ctx contractapi.TransactionContextInterface, payload string) (*Company, error)
func create(ctx contractapi.TransactionContextInterface, payload string) (int, error)
修改後在進行例項化,這次不再報錯了。
但是明明之前的也沒有違反規則,為什麼會報錯呢?想不通為什麼,所以準備給官方提個Issue,萬一真是個bug呢?
結果就在issues裡發現了這個Possible issues with byte[] as return type,一看日期Oct 20, 2021,快兩年了也沒官方的回應......
結論
最後搜了一圈也沒找到原因,檢視原始碼,感覺問題可能是出在 contractFunctionReturns
,具體還得等研究完原始碼之後才能有答案
type contractChaincodeContract struct {
info metadata.InfoMetadata
functions map[string]*internal.ContractFunction
unknownTransaction *internal.TransactionHandler
beforeTransaction *internal.TransactionHandler
afterTransaction *internal.TransactionHandler
transactionContextHandler reflect.Type
}
// ContractChaincode a struct to meet the chaincode interface and provide routing of calls to contracts
type ContractChaincode struct {
DefaultContract string
contracts map[string]contractChaincodeContract
metadata metadata.ContractChaincodeMetadata
Info metadata.InfoMetadata
TransactionSerializer serializer.TransactionSerializer
}
// ContractFunction contains a description of a function so that it can be called by a chaincode
type ContractFunction struct {
function reflect.Value
callType CallType
params contractFunctionParams
returns contractFunctionReturns
}
type contractFunctionReturns struct {
success reflect.Type
error bool
}
宣告:本作品採用署名-非商業性使用-相同方式共享 4.0 國際 (CC BY-NC-SA 4.0)進行許可,使用時請註明出處。
Author: mengbin
blog: mengbin
Github: mengbin92
cnblogs: 戀水無意