Il n'y a pas si longtemps, la première version de fabric-contract-api-go a été publiée - la mise en œuvre du nouveau modèle logiciel pour le code de chaîne RFC 0001 . Voyons ce que c'est et comment l'utiliser.Ici, j'ai préparé un référentiel avec un réseau Fabric simple, où les pairs s'exécutent en mode dev. Suivez les instructions du référentiel pour démarrer le réseau et revenez (cela ne prendra pas plus de 5 minutes).Maintenant que le réseau fonctionne et que le code de chaîne est installé, regardons l'intérieur du code de chaîne fonctionnant dans le nouveau modèle.Dans SimpleContract.go, nous importons le module avec la nouvelle API:github.com/hyperledger/fabric-contract-api-go/contractapi
Ensuite, nous décrivons notre contrat en utilisant la structure SimpleContract dans laquelle la structure Contract est intégrée :type SimpleContract struct {
contractapi.Contract
}
Il est impératif de construire dans Contract afin que notre contrat satisfasse l'interface ContractInterface . Ici, vous devez faire une réservation et dire que le contrat! = Chaincode. Un code de chaîne est un conteneur d'un nombre indéfini de contrats. Chaincode stocke ses contrats sur la carte, comme le montre cette liste:
type ContractChaincode struct {
DefaultContract string
contracts map[string]contractChaincodeContract
metadata metadata.ContractChaincodeMetadata
Info metadata.InfoMetadata
TransactionSerializer serializer.TransactionSerializer
}
Les contrats de carte sont utilisés en interne par Invoke pour acheminer les demandes:func (cc *ContractChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
nsFcn, params := stub.GetFunctionAndParameters()
li := strings.LastIndex(nsFcn, ":")
var ns string
var fn string
if li == -1 {
ns = cc.DefaultContract
fn = nsFcn
} else {
ns = nsFcn[:li]
fn = nsFcn[li+1:]
}
...
nsContract := cc.contracts[ns]
...
successReturn, successIFace, errorReturn = nsContract.functions[fn].Call(ctx, transactionSchema, &cc.metadata.Components, serializer, params...)
...
return shim.Success([]byte(successReturn))
}
Revenons donc à SimpleContract. Toutes les méthodes doivent avoir un paramètre ctx qui satisfait l'interface TransactionContextInterface . Par défaut, toutes les méthodes obtiennent un TransactionContext standard , qui dans la plupart des cas est suffisant.Ce contexte nous permet de travailler avec ClientIdentity , par exemple, comme ceci:func (sc *SimpleContract) Whois(ctx contractapi.TransactionContextInterface) (string, error) {
return ctx.GetClientIdentity().GetID()
}
Ou obtenez le talon familier (shim.ChaincodeStubInterface) pour effectuer toutes les actions habituelles d'interaction avec le registre:func (sc *SimpleContract) Write(ctx contractapi.TransactionContextInterface, key string, value []byte) error {
return ctx.GetStub().PutState(key, value)
}
Mais! Dans le code de notre dépôt de démonstration, vous pouvez voir un contexte complètement différent dans les méthodes:func (sc *SimpleContract) Create(ctx CustomTransactionContextInterface, key string, value string) error {
existing := ctx.GetData()
if existing != nil {
return fmt.Errorf("Cannot create world state pair with key %s. Already exists", key)
}
err := ctx.GetStub().PutState(key, []byte(value))
if err != nil {
return errors.New("Unable to interact with world state")
}
return nil
}
Il s'agit d'un contexte personnalisé. Il est créé très simplement. Faites attention à context.go de notre référentiel:1. Nous déclarons une interface compatible avec contractapi.TransactionContextInterfacetype CustomTransactionContextInterface interface {
contractapi.TransactionContextInterface
GetData() []byte
SetData([]byte)
}
2. La structure dans laquelle nous intégrons contractapi.TransactionContexttype CustomTransactionContext struct {
contractapi.TransactionContext
data []byte
}
3. Nous mettons en œuvre les méthodes déclarées
func (ctc *CustomTransactionContext) GetData() []byte {
return ctc.data
}
func (ctc *CustomTransactionContext) SetData(data []byte) {
ctc.data = data
}
Maintenant, lors de l'initialisation du contrat, nous passons simplement cette structure en tant que gestionnaire:simpleContract := new(SimpleContract)
simpleContract.TransactionContextHandler = new(CustomTransactionContext)
Et toutes les méthodes de notre contrat maintenant au lieu de ctx contractapi.TransactionContextInterface acceptent ctx CustomTransactionContextInterface .Un contexte personnalisé est nécessaire pour pousser un état via des hooks transactionnels . Les crochets transactionnels sont un beau nom pour le middleware qui se déclenche avant ou après l'appel de la méthode contractuelle.Un exemple de hook qui, avant d'appeler une méthode, extrait du registre la valeur de la clé passée par le premier paramètre de la transaction:SimpleContract.gofunc GetWorldState(ctx CustomTransactionContextInterface) error {
_, params := ctx.GetStub().GetFunctionAndParameters()
if len(params) < 1 {
return errors.New("Missing key for world state")
}
existing, err := ctx.GetStub().GetState(params[0])
if err != nil {
return errors.New("Unable to interact with world state")
}
ctx.SetData(existing)
return nil
}
main.gosimpleContract.BeforeTransaction = GetWorldState
Maintenant, nous pouvons obtenir la valeur de la clé demandée dans les méthodes un peu plus concises:SimpleContract.gofunc (sc *SimpleContract) Read(ctx CustomTransactionContextInterface, key string) (string, error) {
existing := ctx.GetData()
if existing == nil {
return "", fmt.Errorf("Cannot read world state pair with key %s. Does not exist", key)
}
return string(existing), nil
}
Le hook après l'appel de méthode est presque identique, sauf qu'en plus du contexte, il accepte une interface vide (pourquoi nous en avons besoin, nous le découvrirons plus tard):YetAnotherContract.gofunc After(ctx contractapi.TransactionContextInterface, beforeValue interface{}) error {
fmt.Println(ctx.GetStub().GetTxID())
fmt.Println("beforeValue", beforeValue)
return nil
}
Ce hook affiche l'ID de transaction et la valeur renvoyée par la méthode avant le hook . Pour vérifier ce posthook, vous pouvez aller dans le conteneur CLI et appeler la méthode du contrat: Basculez vers le terminal où le code est exécuté, la sortie ressemblera à ceci:docker exec -it cli sh
peer chaincode query -n mycc -c '{"Args":["YetAnotherContract:SayHi"]}' -C myc
e503e98e4c71285722f244a481fbcbf0ff4120adcd2f9067089104e5c3ed0efe # txid
beforeValue Hi there # valeur de la méthode précédente
Et si nous voulons traiter les requêtes avec un nom de fonction inexistant? Pour cela, un contrat a un UnknownTransaction domaine :unknown_handler.gofunc UnknownTransactionHandler(ctx CustomTransactionContextInterface) error {
fcn, args := ctx.GetStub().GetFunctionAndParameters()
return fmt.Errorf("Invalid function %s passed with args %v", fcn, args)
}
main.gosimpleContract.UnknownTransaction = UnknownTransactionHandler
Cela peut également être vérifié via la CLI: Conclusion:docker exec -it cli sh
peer chaincode query -n mycc -c '{"Args":["BadRequest", "BadKey"]}' -C myc
Erreur: échec de l'approbation lors de la requête. réponse: état: 500 message: "Fonction non valide BadRequest passée avec args [BadKey]"
Pour que le code démarre sur le pair, nous devons appeler la méthode Start () comme avant, avant de transférer tous nos contrats vers le code :main.gocc, err := contractapi.NewChaincode(simpleContract, yetAnotherContract)
if err != nil {
panic(err.Error())
}
if err := cc.Start(); err != nil {
panic(err.Error())
}
Total
Le nouveau modèle du codecode a résolu le problème de routage, middleware, sérialisation des valeurs de retour, désérialisation des arguments de chaîne (tous les types sauf l' interface {} peuvent être utilisés ). Il reste maintenant à attendre la mise en œuvre du nouveau modèle pour le SDK Go.Merci pour l'attention.Deuxième partie