Hyperledger Fabric (HLF) est une plate-forme open source qui utilise la technologie de registre distribué (DLT), conçue pour développer des applications qui fonctionnent dans un environnement de réseau d'entreprise créé et contrôlé par un consortium d'organisations utilisant des règles d'autorisation.
La plateforme prend en charge les contrats intelligents, en termes HLF - des codes de chaîne créés dans des langages généraux tels que Golang, JavaScript, Java, contrairement, par exemple, à Ethereum, qui utilise un langage Solidity axé sur les contrats et à fonctionnalités limitées. (LLL, Viper, etc.).

Le développement et le test de codes de chaîne, en raison de la nécessité de déployer un nombre important de composants du réseau blockchain, peuvent être un processus assez long avec un temps important consacré à tester les changements. Cet article présente une approche du développement et des tests rapides des contrats intelligents HLF Golang à l'aide de la bibliothèque CCKit .
Application basée sur HLF
Du point de vue du développeur, l'application blockchain se compose de deux parties principales:
- On-chain - contrats intelligents (programmes) fonctionnant dans un environnement isolé du réseau blockchain qui déterminent les règles de création et la composition des attributs de transaction. Dans un contrat intelligent, les principales actions sont la lecture, la mise à jour et la suppression de données de l'état du réseau blockchain. Il convient de souligner que la suppression de données d'un état laisse supposer que ces données étaient présentes.
- Off-chain est une application (par exemple, une API) qui interagit avec l'environnement blockchain via le SDK. L'interaction consiste à appeler des fonctions de contrat intelligent et à surveiller des événements de contrat intelligent - les événements externes peuvent entraîner des modifications de données dans le contrat intelligent, tandis que les événements dans le contrat intelligent peuvent déclencher des actions dans des systèmes externes.
Les données sont généralement lues via le nœud de réseau blockchain «home». Pour enregistrer les données, l'application envoie des requêtes aux nœuds des organisations participant à la «politique d'approbation» d'un contrat intelligent particulier.
Pour développer du code hors chaîne (API, etc.), un SDK spécialisé est utilisé qui encapsule l'interaction avec les nœuds de la chaîne de blocs, la collecte des réponses, etc. Pour HLF, il existe des implémentations SDK pour Go ( 1 , 2 ), Node.Js et Java
Composants de tissu Hyperledger
Chaîne
Un canal est un sous-réseau distinct de nœuds qui prend en charge une chaîne de blocs isolée (registre), ainsi que l'état actuel (valeur-clé) de la chaîne de blocs ( état mondial ) utilisé pour gérer les contrats intelligents. Un hôte peut avoir accès à un nombre arbitraire de canaux.
Transaction
Une transaction dans Hyperledger Fabric est une mise à jour atomique de l'état d'une chaîne de blocs, résultat de l'exécution de la méthode chaincode. Une transaction consiste en une demande d'appel de la méthode chaincode avec quelques arguments (proposition de transaction) signés par le nœud appelant et un ensemble de réponses (réponse de proposition de transaction) des nœuds sur lesquels la transaction a été «confirmée» (avenant). Les réponses contiennent des informations sur les paires clé-valeur changeantes de l'état de la chaîne de blocs de l' ensemble lecture-écriture et des informations de service (signatures et certificats des nœuds confirmant la transaction). Parce que les chaînes de blocs de canaux individuels sont physiquement séparées, une transaction ne peut être effectuée que dans le contexte d'un canal.
Les plateformes de blockchain "classiques", telles que Bitcoin et Ethereum , utilisent le cycle de transaction Ordering-Execution exécuté par tous les nœuds, ce qui limite l'évolutivité du réseau de blockchain.

Hyperledger Fabric utilise une architecture d'exécution et de distribution de transactions qui comporte 3 opérations principales:
Exécution ( exécution ) - création par un contrat intelligent s'exécutant sur un ou plusieurs nœuds de réseau, transactions - changements atomiques dans l'état d'un registre distribué ( avenant )
Commande - commande et regroupement des transactions en blocs par le service de commande spécialisé à l'aide d'un algorithme de consensus enfichable.
Valider - vérification par les nœuds du réseau des transactions provenant du donneur d' ordre avant de placer des informations de leur part dans leur copie du registre distribué

Cette approche vous permet d'effectuer la phase d'exécution de la transaction avant son entrée dans le réseau de la blockchain, ainsi que de dimensionner horizontalement le fonctionnement des nœuds du réseau.
Chaincode
Un code de chaîne, qui peut également être appelé un contrat intelligent, est un programme écrit en Golang, JavaScript (HLF 1.1+) ou Java (HLF 1.3+), qui définit les règles de création de transactions qui modifient l'état d'une chaîne de blocs. Le programme est exécuté simultanément sur plusieurs nœuds indépendants du réseau distribué de nœuds blockchain, ce qui crée un environnement neutre pour l'exécution des contrats intelligents en réconciliant les résultats du programme sur tous les nœuds nécessaires à la "confirmation" de la transaction.
Le code doit implémenter une interface composée de méthodes:
type Chaincode interface {
- La méthode Init est appelée lors de l'instanciation ou de la mise à niveau du codecode. Cette méthode effectue l'initialisation nécessaire de l'état du codecode. Il est important de distinguer dans le code de méthode si l'appel est une instanciation ou une mise à niveau, afin que par erreur vous n'initialisiez pas (réinitialisiez) les données qui ont déjà reçu un état non nul pendant le fonctionnement du codecode.
- La méthode Invoke est appelée lorsque n'importe quelle fonction du code de code est accessible. Cette méthode fonctionne avec le statut des contrats intelligents.
Le chaincode est installé sur les pairs du réseau blockchain. Au niveau du système, chaque instance du code correspond à un docker-container distinct attaché à un nœud de réseau spécifique, qui effectue des appels de répartition à l'exécution du code.
Contrairement aux contrats intelligents Ethereum, la logique de chaînage peut être mise à jour, mais cela nécessite que tous les nœuds qui hébergent le code de code installent une version mise à jour.
En réponse à un appel à la fonction chaincode de l'extérieur via le SDK, le chaincode crée un changement d'état de la chaîne de blocs ( Read-Write Set ), ainsi que des événements. Un code de chaîne fait référence à un canal spécifique et peut modifier les données dans un seul canal. Dans le même temps, si l'hôte sur lequel le code est installé a également accès à d'autres canaux, dans la logique du code peut être la lecture des données de ces canaux.
Les codes de chaîne spéciaux pour gérer divers aspects du fonctionnement d'un réseau de chaînes de blocs sont appelés codes de chaîne système.
Politique d'approbation
Une politique d'approbation définit des règles de consensus au niveau des transactions générées par un code de chaîne spécifique. La stratégie définit les règles qui déterminent quels nœuds de canal doivent créer une transaction. Pour ce faire, chacun des nœuds spécifiés dans la politique d'approbation doit exécuter la méthode de chaînage (étape "Exécuter"), effectuer une "simulation", après quoi les résultats signés seront collectés et vérifiés par le SDK qui a initié la transaction (tous les résultats de simulation doivent être identiques, les signatures de tous les nœuds requis par la politique doivent être présentes). Ensuite, le SDK envoie la transaction à orderer , après quoi tous les nœuds qui ont accès au canal recevront la transaction via orderer et effectueront l'étape "Valider". Il est important de souligner que tous les nœuds de canal ne doivent pas participer à l'étape "Exécuter".
La politique d'approbation est déterminée au moment de l'instanciation ou de la mise à niveau du code. Dans la version 1.3, il est devenu possible de définir des stratégies non seulement au niveau du code de chaîne, mais également au niveau des clés d'approbation individuelles basées sur l'état . Exemples de politiques d'approbation:
- Noeuds A, B, C, D
- La plupart des nœuds de canal
- Au moins 3 nœuds de A, B, C, D, E, F
Événement
Un événement est un ensemble de données nommé qui vous permet de publier un «flux de mise à jour» de l'état de la chaîne de blockchain. L'ensemble des attributs d'événement définit le code de chaîne.
Infrastructure réseau
Hôte (pair)
Un hôte est connecté à un nombre arbitraire de canaux pour lesquels il dispose de droits d'accès. L'hôte conserve sa version de la chaîne de blocs et l'état de la chaîne de blocs, et fournit également un environnement pour l'exécution des codes de chaîne. Si l'hôte ne fait pas partie de la politique d'approbation, il n'est pas nécessaire de la configurer avec des codes de chaîne.
Au niveau du logiciel hôte, l'état actuel de la chaîne de blocs (état mondial) peut être stocké dans LevelDB ou dans CouchDB. L'avantage de CouchDB est sa prise en charge des requêtes riches utilisant la syntaxe MongoDB.
Commander
Le service de gestion des transactions accepte les transactions signées en entrée et garantit que les transactions sont réparties sur les nœuds du réseau dans le bon ordre.
Orderer n'exécute pas de contrats intelligents et ne contient pas de chaînes de blocs et d'états de chaînes de blocs. À l'heure actuelle (1.3), il existe deux implémentations de Orderer - un développement solo et une version basée sur Kafka qui fournit une tolérance aux pannes. Une mise en œuvre de la commande supportant la résistance au comportement incorrect d'une certaine fraction des participants (tolérance aux pannes byzantine) est attendue fin 2018.
Services d'identité
Dans un réseau Hyperledger Fabric, tous les membres ont des identités connues des autres membres (identité). Pour l'identification, l'infrastructure à clé publique (PKI) est utilisée, à travers laquelle les certificats X.509 sont créés pour les organisations, les éléments d'infrastructure (nœud, ordre), les applications et les utilisateurs finaux. Par conséquent, l'accès à la lecture et à la modification des données peut être contrôlé par des règles d'accès au niveau du réseau, sur un seul canal ou dans la logique d'un contrat intelligent. Dans le même réseau blockchain, plusieurs services d'identification de différents types peuvent fonctionner simultanément.
Implémentation du codecode
Le code de chaîne peut être considéré comme un objet doté de méthodes qui implémentent une logique métier spécifique. Contrairement à la POO classique, un code de chaîne ne peut pas avoir de champs d'attribut. Pour travailler avec l'état, dont le stockage est fourni par la plateforme de chaîne de blocs HLF, la couche ChaincodeStubInterface est utilisée , qui est transmise lorsque les méthodes Init et Invoke sont appelées. Il offre la possibilité de recevoir des arguments d'appel de fonction et d'apporter des modifications à l'état de la chaîne de blocs:
type ChaincodeStubInterface interface {
Dans le contrat intelligent Ethereum développé sur Solidity, chaque méthode a une fonction publique. Dans le chaincode Hyperledger Fabric dans les méthodes Init et Invoke à l'aide de la fonction ChaincodeStubInterface . GetArgs (), vous pouvez obtenir les arguments de l'appel de fonction sous la forme d'un tableau de tableaux d'octets, tandis que le premier élément du tableau lors de l'appel à Invoke contient le nom de la fonction chaincode. Parce que L'invocation de n'importe quelle méthode de chaincode passe par la méthode Invoke; nous pouvons dire qu'il s'agit d'une implémentation du modèle de contrôleur frontal.
Par exemple, si nous considérons la mise en œuvre de l'interface Ethereum standard pour le jeton ERC-20 , le contrat intelligent devrait implémenter les méthodes:
- totalSupply ()
- balanceOf (adresse _propriétaire)
- transfert (adresse _à, uint256 _valeur)
Dans le cas de l'implémentation HLF, le code de fonction Invoke doit être capable de gérer les cas où le premier argument des appels Invoke contient le nom des méthodes attendues (par exemple, «totalSupply» ou «balanceOf»). Un exemple de mise en œuvre de la norme ERC-20 peut être vu ici .
Exemples de code de chaîne
En plus de la documentation Hyperledger Fabric , il existe quelques autres exemples de codes de chaîne:
L'implémentation des codes de chaîne dans ces exemples est assez verbeuse et contient beaucoup de logique répétitive pour sélectionner les fonctions de routage appelées), vérifier le nombre d'arguments, json marshalling / unmarshalling:
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { function, args := stub.GetFunctionAndParameters() fmt.Println("invoke is running " + function)
Une telle organisation du code entraîne une détérioration de la lisibilité du code et des erreurs possibles, telles que celle-ci , lorsque vous avez simplement oublié de dé-déclarer les données d'entrée. Les présentations concernant les plans de développement HLF mentionnent une révision de l'approche pour le développement des codes de chaîne, en particulier l'introduction d'annotations dans les codes de chaîne Java, etc., cependant, les plans concernent la version qui n'est attendue qu'en 2019. L'expérience du développement de contrats intelligents a conduit à la conclusion que le développement et le test de codes de chaîne seront plus faciles si vous sélectionnez les fonctionnalités de base dans une bibliothèque séparée.
CCKit - une bibliothèque pour développer et tester des codes de chaîne
La bibliothèque CCKit résume la pratique de développement et de test des codes de chaîne. Dans le cadre du développement des extensions de chaincode , la bibliothèque d'extensions OpenZeppelin pour les contrats intelligents Ethereum a été utilisée comme exemple. CCKit utilise les solutions architecturales suivantes:
Acheminer les appels vers les fonctions de contrat intelligent
Le routage fait référence à l'algorithme par lequel l'application répond à une demande client. Cette approche est utilisée, par exemple, dans presque tous les frameworks http. Le routeur utilise certaines règles pour lier la demande et le gestionnaire de demandes. Par rapport à un code de chaîne, il s'agit d'associer le nom de la fonction de code de chaîne à la fonction de gestionnaire.
Dans les derniers exemples de contrats intelligents, par exemple, dans l' application d'assurance , cela utilise le mappage entre le nom de la fonction chaincode et la fonction dans le code Golang du formulaire:
var bcFunctions = map[string]func(shim.ChaincodeStubInterface, []string) pb.Response{
Le routeur CCKit utilise une approche similaire au routeur http, ainsi que la possibilité d'utiliser le contexte de demande pour la fonction chaincode et les fonctions middleware
Le contexte de l'appel à la fonction du code
Semblable au contexte de demande http, qui a généralement accès aux paramètres de demande http, le routeur CCKit utilise le contexte de l'appel à la fonction de contrat intelligent , qui est une abstraction au-dessus de shim.ChaincodeStubInterface . Le contexte peut être le seul argument du gestionnaire de la fonction de chaînage; à travers lui, le gestionnaire peut recevoir les arguments de l'appel de fonction, ainsi que l'accès aux fonctionnalités auxiliaires pour travailler avec l'état du contrat intelligent (État), créer des réponses (Réponse), etc.
Context interface { Stub() shim.ChaincodeStubInterface Client() (cid.ClientIdentity, error) Response() Response Logger() *shim.ChaincodeLogger Path() string State() State Time() (time.Time, error) Args() InterfaceMap Arg(string) interface{} ArgString(string) string ArgBytes(string) []byte SetArg(string, interface{}) Get(string) interface{} Set(string, interface{}) SetEvent(string, interface{}) error }
Parce que Le contexte est une interface, dans certains codes de chaîne, il peut être étendu.
Fonctions middleware
Les fonctions de traitement intermédiaire (middleware) sont appelées avant l'appel du gestionnaire de la méthode du code, ont accès au contexte de l'appel à la méthode du code et à la fonction intermédiaire suivante ou directement au gestionnaire de la méthode de la suivante (suivante). Le middleware peut être utilisé pour:
- conversion des données d'entrée (dans l'exemple ci-dessous, p.String et p.Struct sont des middlewares)
- restrictions d'accès à la fonction (par exemple, propriétaire uniquement )
- achèvement du cycle de traitement des demandes
- appeler la prochaine fonction de traitement intermédiaire à partir de la pile
Conversion de la structure des données
L'interface chaincode suppose qu'un tableau de tableaux d'octets est fourni à l'entrée, dont chacun des éléments est un attribut de la fonction chaincode. Afin d'empêcher le marshaling manuel des données du tableau d'octets vers le type de données golang (int, chaîne, structure, tableau) des arguments d'appel de fonction dans chaque gestionnaire de la fonction de chaînage, les types de données attendus sont définis au moment de la création de la règle de routage dans le routeur CCKit et le type est converti automatiquement . Dans l'exemple qui suit , la fonction carGet attend un argument de type chaîne et la fonction carRegister attend une structure CarPayload . L'argument est également nommé, ce qui permet au gestionnaire d'obtenir sa valeur du contexte par son nom. Un exemple de gestionnaire sera donné ci-dessous. Protobuf peut également être utilisé pour décrire le schéma de données de chaînage.
r.Group(`car`). Query(`List`, cars).
De plus, la conversion automatique (marshalling) est utilisée lors de l'écriture de données à l'état d'un contrat intelligent et lors de la création d'événements (le type golang est sérialisé en un tableau d'octets)
Outils de débogage et de journalisation des codes de chaîne
Pour déboguer le code, vous pouvez utiliser l'extension de débogage , qui implémente des méthodes de contrat intelligent qui vous permettront d'inspecter la présence de clés dans l'état du contrat intelligent, ainsi que de lire / modifier / supprimer directement la valeur par clé.
Pour la journalisation dans le contexte d'un appel à une fonction chaincode, la méthode Log () peut être utilisée, qui retourne une instance de l'enregistreur utilisé dans HLF.
Méthodes de contrat intelligentes méthodes de contrôle d'accès
Dans le cadre de l'extension propriétaire , des primitives de base pour stocker des informations sur le propriétaire du code de chaîne instancié et des modificateurs d'accès (middleware) pour les méthodes de contrat intelligentes sont implémentées.
Outils de test de contrat intelligent
Déployer le réseau blockchain, installer et initialiser des codes de chaîne est une configuration assez compliquée et une longue procédure. Le temps de réinstallation / mise à niveau du code de contrat intelligent peut être réduit en utilisant le mode DEV du contrat intelligent, cependant, le processus de mise à jour du code sera toujours lent.
Le package shim contient une implémentation de MockStub , qui encapsule les appels au code du code, simulant son fonctionnement dans l'environnement de blockchain HLF. L'utilisation de MockStub vous permet d'obtenir des résultats de test presque instantanément et de réduire le temps de développement. Si nous considérons le schéma général de fonctionnement du code en HLF, MockStub remplace essentiellement le SDK, vous permettant d'appeler les fonctions du code et imite l'environnement de démarrage du code sur l'hôte.

Le MockStub de la livraison HLF contient l'implémentation de presque toutes les méthodes de l'interface shim.ChaincodeStubInterface , cependant, dans la version actuelle (1.3), il manque l'implémentation de certaines méthodes importantes, telles que GetCreator. Parce que le code de chaîne peut utiliser cette méthode pour obtenir un certificat d'un créateur de transaction pour le contrôle d'accès, pour une couverture maximale dans les tests, la possibilité d'avoir un talon de cette méthode est importante.
La bibliothèque CCKit contient une version étendue de MockStub , qui contient l'implémentation des méthodes manquantes, ainsi que des méthodes de travail avec les canaux d'événements, etc.
Exemple de code de chaîne
Par exemple, créons un code de chaîne simple pour stocker des informations sur les voitures enregistrées
Modèle de données
L'état du code de code est le stockage de valeur-clé, dans lequel la clé est une chaîne, la valeur est un tableau d'octets. La pratique de base consiste à stocker des instances de structures de données golang jonalisées en tant que valeurs. Par conséquent, pour travailler avec des données dans le code de chaîne, après avoir lu à partir de l'état, vous devez désarchiver le tableau d'octets.
Pour enregistrer sur la voiture, nous utiliserons l'ensemble d'attributs suivant:
- Identifiant (numéro de voiture)
- Modèle de voiture
- Informations sur le propriétaire du véhicule
- Informations sur l'heure de changement des données
Pour transférer des données vers le code de chaîne, créez une structure distincte contenant uniquement les champs provenant de l'extérieur du code de chaîne:
Travailler avec des clés
Les clés d'enregistrement dans un état de contrat intelligent sont une chaîne. Il prend également en charge la possibilité de créer des clés composites dans lesquelles des parties de la clé sont séparées par un octet zéro ( U + 0000 )
func CreateCompositeKey(objectType string, attributes []string) (string, error)
Dans CCKit, les fonctions de travail avec l'état d'un contrat intelligent peuvent créer automatiquement des clés pour les enregistrements si les structures transférées prennent en charge l'interface Keyer
Pour enregistrer une voiture, la fonction de génération de clé sera la suivante:
const CarEntity = `CAR`
Déclaration de fonction de contrat intelligent (routage)
Dans la méthode constructeur du chaincode, nous pouvons définir les fonctions du chaincode et leurs arguments. Il y aura 3 fonctions dans le code d'immatriculation de la voiture
- carList, renvoie un tableau de structures Car
- carGet, accepte un identifiant de voiture et renvoie une structure Car
- carRegister, accepte une instance sérialisée de la structure CarPayload et renvoie le résultat de l'enregistrement. L'accès à cette méthode n'est possible que pour le propriétaire du code de chaîne, qui est enregistré à l'aide du middleware du package propriétaire
func New() *router.Chaincode { r := router.New(`cars`)
L'exemple ci-dessus utilise la structure Chaincode dans laquelle le traitement des méthodes Init et Invoke est délégué au routeur:
package router import ( "github.com/hyperledger/fabric/core/chaincode/shim" "github.com/hyperledger/fabric/protos/peer" )
L'utilisation d'un routeur et de la structure de base de Chaincode permet la réutilisation des fonctions du gestionnaire. Par exemple, pour implémenter chaincode sans vérifier l'accès à la fonction carRegister
, carRegister
suffira de créer une nouvelle méthode constructeur
Mise en place des fonctions d'un contrat intelligent
Fonctions Golang - Les gestionnaires de fonctions de contrat intelligentes dans le routeur CCKit peuvent être de trois types:
- StubHandlerFunc - l'interface de gestionnaire standard, accepte shim.ChaincodeStubInterface , renvoie la réponse standard homologue.
- ContextHandlerFunc - prend un contexte et renvoie un pair.
- HandlerFunc - prend un contexte, retourne une interface et une erreur. Un tableau d'octets peut être renvoyé ou n'importe quel type de golang qui est automatiquement converti en un tableau d'octets en fonction du peer.Response créé. Le statut de la réponse sera shim.Ok ou shim.Error , selon l'erreur transmise.
, , ( CarPayload)
State , ( )
-
- — , . BDD – Behavior Driven Development, .
, , - Ethereum ganache-cli truffle . golang - Mockstub.
, . .
Ginkgo , Go, go test
. gomega expect , , .
import ( "testing" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" examplecert "github.com/s7techlab/cckit/examples/cert" "github.com/s7techlab/cckit/extensions/owner" "github.com/s7techlab/cckit/identity" "github.com/s7techlab/cckit/state" testcc "github.com/s7techlab/cckit/testing" expectcc "github.com/s7techlab/cckit/testing/expect" ) func TestCars(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Cars Suite") }
, CarPayload :
var Payloads = []*Car{{ Id: `A777MP77`, Title: `VAZ`, Owner: `victor`, }, { Id: `O888OO77`, Title: `YOMOBIL`, Owner: `alexander`, }, { Id: `O222OO177`, Title: `Lambo`, Owner: `hodl`, }}
MockStub Cars.
Parce que cars , .
BeforeSuite Car authority Init . , Cars Init Init , .
BeforeSuite(func() {
. , CarRegister , .
It("Allow authority to add information about car", func() {
:
It("Disallow authority to add duplicate information about car", func() { expectcc.ResponseError( cc.From(actors[`authority`]).Invoke(`carRegister`, Payloads[0]), state.ErrKeyAlreadyExists)
Conclusion
- HLF Go, Java, JavaScript, , , - (Solidity) / -. / .
HLF , , ( .). Hypeledger Fabric , .. .