Nouveau modèle logiciel pour le code de chaîne Hyperledger Fabric



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.TransactionContextInterface

type CustomTransactionContextInterface interface {
	contractapi.TransactionContextInterface
	GetData() []byte
	SetData([]byte)
}

2. La structure dans laquelle nous intégrons contractapi.TransactionContext

type CustomTransactionContext struct {
	contractapi.TransactionContext
	data []byte
}

3. Nous mettons en œuvre les méthodes déclarées

// GetData return set data
func (ctc *CustomTransactionContext) GetData() []byte {
	return ctc.data
}

// SetData provide a value for 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.go
func 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.go
simpleContract.BeforeTransaction = GetWorldState

Maintenant, nous pouvons obtenir la valeur de la clé demandée dans les méthodes un peu plus concises:

SimpleContract.go
func (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.go
func 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.go
func UnknownTransactionHandler(ctx CustomTransactionContextInterface) error {
	fcn, args := ctx.GetStub().GetFunctionAndParameters()
	return fmt.Errorf("Invalid function %s passed with args %v", fcn, args)
}

main.go
simpleContract.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.go
cc, 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

All Articles