Entwicklung und Testen intelligenter Verträge für Hyperledger Fabric

Hyperledger Fabric (HLF) ist eine Open-Source-Plattform, die DLT-Technologie (Distributed Ledger Technology) verwendet, um Anwendungen zu entwickeln, die in einer Unternehmensnetzwerkumgebung funktionieren, die von einem Konsortium von Organisationen mithilfe von Zugriffsregeln erstellt und gesteuert wird (mit Berechtigung).


Die Plattform unterstützt intelligente Verträge in HLF-Begriffen - Kettencodes, die in allgemeinen Sprachen wie Golang, JavaScript, Java erstellt wurden, im Gegensatz zu beispielsweise Ethereum, das eine vertragsorientierte Solidity-Sprache mit eingeschränkter Funktionalität verwendet (LLL, Viper usw.).



Das Entwickeln und Testen von Kettencodes kann aufgrund der Notwendigkeit, eine erhebliche Anzahl von Komponenten des Blockchain-Netzwerks bereitzustellen, ein ziemlich langwieriger Prozess sein, bei dem viel Zeit für das Testen von Änderungen aufgewendet wird. Dieser Artikel beschreibt einen Ansatz zur schnellen Entwicklung und zum Testen von HLF Golang-Smart-Verträgen mithilfe der CCKit- Bibliothek.


HLF-basierte Anwendung


Aus Sicht des Entwicklers besteht die Blockchain-Anwendung aus zwei Hauptteilen:


  • On-Chain - Intelligente Verträge (Programme), die in einer isolierten Umgebung des Blockchain-Netzwerks ausgeführt werden und die Regeln für die Erstellung und Zusammensetzung von Transaktionsattributen festlegen. In einem intelligenten Vertrag sind die Hauptaktionen das Lesen, Aktualisieren und Löschen von Daten aus dem Status des Blockchain-Netzwerks. Es sollte betont werden, dass beim Löschen von Daten aus einem Status Informationen darüber verbleiben, dass diese Daten vorhanden waren.
  • Off-Chain ist eine Anwendung (z. B. eine API), die über das SDK mit der Blockchain-Umgebung interagiert. Unter Interaktion versteht man das Aufrufen von Smart Contract-Funktionen und das Überwachen von Smart Contract-Ereignissen. Externe Ereignisse können Datenänderungen im Smart Contract verursachen, während Ereignisse im Smart Contract Aktionen in externen Systemen auslösen können.

Daten werden normalerweise über den Blockchain-Netzwerkknoten „Home“ gelesen. Um Daten aufzuzeichnen, sendet die Anwendung Anforderungen an die Knoten von Organisationen, die an der „Genehmigungsrichtlinie“ eines bestimmten Smart-Vertrags teilnehmen.


Zur Entwicklung von Off-Chain-Code (API usw.) wird ein spezielles SDK verwendet, das die Interaktion mit Blockchain-Knoten, das Sammeln von Antworten usw. kapselt. Für HLF gibt es SDK-Implementierungen für Go ( 1 , 2 ), Node.Js und Java


Hyperledger Fabric-Komponenten


Kanal


Ein Kanal ist ein separates Subnetz von Knoten, das eine isolierte Blockkette (Ledger) sowie den aktuellen Status (Schlüsselwert) der Blockkette (Weltstatus) unterstützt, die für den Betrieb intelligenter Verträge verwendet wird. Ein Host kann auf eine beliebige Anzahl von Kanälen zugreifen.


Transaktion


Eine Transaktion in Hyperledger Fabric ist eine atomare Aktualisierung des Status einer Blockkette, das Ergebnis der Ausführung der Chaincode-Methode. Eine Transaktion besteht aus einer Anforderung zum Aufrufen einer Chaincode-Methode mit einigen vom aufrufenden Knoten signierten Argumenten (Transaktionsvorschlag) und einer Reihe von Antworten (Transaktionsvorschlagsantwort) von den Knoten, auf denen die Transaktion „bestätigt“ wurde (Bestätigung). Die Antworten enthalten Informationen zu den sich ändernden Schlüssel-Wert-Paaren des Status der Blockkette des Lese- / Schreibsatzes und Dienstinformationen (Signaturen und Zertifikate von Knoten, die die Transaktion bestätigen). Weil Blockketten einzelner Kanäle sind physisch getrennt, eine Transaktion kann nur im Kontext eines Kanals durchgeführt werden.


Die "klassischen" Blockchain-Plattformen wie Bitcoin und Ethereum verwenden den von allen Knoten ausgeführten Ordering-Execution-Transaktionszyklus, wodurch die Skalierbarkeit des Blockchain-Netzwerks eingeschränkt wird.



Hyperledger Fabric verwendet eine Transaktionsausführungs- und -verteilungsarchitektur mit drei Hauptoperationen:


  • Ausführung ( Ausführung ) - Erstellung durch einen intelligenten Vertrag, der auf einem oder mehreren Netzwerkknoten ausgeführt wird, Transaktionen - atomare Änderungen im Status einer verteilten Registrierung ( Bestätigung )


  • Bestellung - Bestellung und Gruppierung von Transaktionen in Blöcken durch den spezialisierten Bestellservice unter Verwendung eines steckbaren Konsensalgorithmus.


  • Validieren - Überprüfung der vom Besteller stammenden Transaktionen durch Netzwerkknoten, bevor Informationen von diesen in ihre Kopie der verteilten Registrierung aufgenommen werden




Mit diesem Ansatz können Sie die Transaktionsausführungsphase vor dem Eintritt in das Blockchain-Netzwerk ausführen und den Betrieb von Netzwerkknoten horizontal skalieren.


Kettencode


Ein Kettencode, der auch als Smart Contract bezeichnet werden kann, ist ein in Golang, JavaScript (HLF 1.1+) oder Java (HLF 1.3+) geschriebenes Programm, das die Regeln zum Erstellen von Transaktionen definiert, die den Status einer Blockkette ändern. Das Programm wird gleichzeitig auf mehreren unabhängigen Knoten eines verteilten Netzwerks von Blockchain-Knoten ausgeführt, wodurch eine neutrale Umgebung für die Ausführung intelligenter Verträge geschaffen wird, indem die Ergebnisse der Programmausführung auf allen Knoten abgeglichen werden, die für die "Bestätigung" der Transaktion erforderlich sind.


Der Code muss eine Schnittstelle implementieren, die aus Methoden besteht:


type Chaincode interface { // Init is called during Instantiate transaction Init(stub ChaincodeStubInterface) pb.Response // Invoke is called to update or query the ledger Invoke(stub ChaincodeStubInterface) pb.Response } 

  • Die Init- Methode wird beim Instanziieren oder Aktualisieren des Codecodes aufgerufen. Diese Methode führt die erforderliche Initialisierung des Status des Codecodes durch. Es ist wichtig, im Methodencode zu unterscheiden, ob es sich bei dem Aufruf um eine Instanziierung oder ein Upgrade handelt, damit Sie versehentlich nicht die Daten initialisieren (zurücksetzen), die während des Betriebs des Codecodes bereits einen Status ungleich Null erhalten haben.
  • Die Invoke- Methode wird aufgerufen, wenn auf eine Funktion des Codecodes zugegriffen wird. Diese Methode funktioniert mit dem Status intelligenter Verträge.

Der Kettencode wird auf den Peers des Blockchain-Netzwerks installiert. Auf Systemebene entspricht jede Instanz des Codes einem separaten Docker-Container, der an einen bestimmten Netzwerkknoten angeschlossen ist und Dispatching-Aufrufe zur Ausführung des Codes ausführt.
Im Gegensatz zu intelligenten Ethereum-Verträgen kann die Verkettungslogik aktualisiert werden. Dies erfordert jedoch, dass alle Knoten, auf denen sich der Codecode befindet, eine aktualisierte Version installieren.


In Reaktion auf einen Aufruf der Kettencodefunktion von außen über das SDK erzeugt der Kettencode eine Änderung des Status der Blockkette ( Lese- / Schreibsatz ) sowie Ereignisse. Ein Kettencode bezieht sich auf einen bestimmten Kanal und kann Daten in nur einem Kanal ändern. Wenn der Host, auf dem der Code installiert ist, auch Zugriff auf andere Kanäle hat, kann gleichzeitig in der Logik des Codes Daten von diesen Kanälen gelesen werden.


Spezielle Kettencodes zum Verwalten verschiedener Aspekte des Betriebs eines Blockchain-Netzwerks werden als Systemkettencodes bezeichnet.


Endorsement Policy


Eine Genehmigungsrichtlinie definiert Konsensregeln auf der Ebene der Transaktionen, die von einem bestimmten Kettencode generiert werden. Die Richtlinie legt die Regeln fest, die bestimmen, welche Kanalknoten eine Transaktion erstellen sollen. Zu diesem Zweck muss jeder der in der Genehmigungsrichtlinie angegebenen Knoten die Verkettungsmethode (Schritt "Ausführen") ausführen und eine "Simulation" durchführen. Anschließend werden die signierten Ergebnisse vom SDK gesammelt und überprüft, das die Transaktion initiiert hat (alle Simulationsergebnisse müssen identisch sein.) Signaturen aller für die Richtlinie erforderlichen Knoten müssen vorhanden sein. Als nächstes sendet das SDK die Transaktion an den Besteller. Danach empfangen alle Knoten, die Zugriff auf den Kanal haben, die Transaktion über den Besteller und führen den Schritt "Validieren" aus. Es ist wichtig zu betonen, dass nicht alle Kanalknoten am Schritt "Ausführen" teilnehmen müssen.


Die Genehmigungsrichtlinie wird zum Zeitpunkt der Instanziierung oder Aktualisierung des Codes festgelegt. In Version 1.3 wurde es möglich, Richtlinien nicht nur auf der Ebene des Kettencodes, sondern auch auf der Ebene der einzelnen zustandsbasierten Endorsement-Schlüssel festzulegen. Beispiele für Genehmigungsrichtlinien:


  • Knoten A, B, C, D.
  • Die meisten Kanalknoten
  • Mindestens 3 Knoten von A, B, C, D, E, F.

Ereignis


Ein Ereignis ist ein benannter Datensatz, mit dem Sie einen „Update-Feed“ des Status der Blockchain-Kette veröffentlichen können. Der Satz von Ereignisattributen definiert den Kettencode.


Netzwerkinfrastruktur


Host (Peer)


Ein Host ist mit einer beliebigen Anzahl von Kanälen verbunden, für die er Zugriffsrechte hat. Der Host verwaltet seine Version der Blockkette und den Status der Blockkette und bietet auch eine Umgebung zum Ausführen von Kettencodes. Wenn der Host nicht Teil der Genehmigungsrichtlinie ist, muss er nicht mit Kettencodes eingerichtet werden.


Auf der Ebene der Host-Software kann der aktuelle Status der Blockkette (Weltstatus) in LevelDB oder in CouchDB gespeichert werden. Der Vorteil von CouchDB ist die Unterstützung umfangreicher Abfragen mit MongoDB-Syntax.


Besteller


Der Transaktionsverwaltungsdienst akzeptiert signierte Transaktionen als Eingabe und stellt sicher, dass die Transaktionen in der richtigen Reihenfolge auf die Netzwerkknoten verteilt werden.


Der Besteller führt keine intelligenten Verträge aus und enthält keine Blockketten und Blockkettenzustände. Im Moment (1.3) gibt es zwei Implementierungen von orderer - ein Development Solo und eine auf Kafka basierende Version, die Crash-Fehlertoleranz bietet. Eine Implementierung des Auftraggebers , der den Widerstand gegen das falsche Verhalten eines bestimmten Teils der Teilnehmer unterstützt (byzantinische Fehlertoleranz), wird Ende 2018 erwartet.


Identitätsdienste


In einem Hyperledger Fabric-Netzwerk haben alle Mitglieder Identitäten, die anderen Mitgliedern bekannt sind (Identität). Zur Identifizierung wird die Public-Key-Infrastruktur (PKI) verwendet, über die X.509-Zertifikate für Organisationen, Infrastrukturelemente (Knoten, Besteller), Anwendungen und Endbenutzer erstellt werden. Infolgedessen kann der Zugriff auf Lese- und Änderungsdaten durch Zugriffsregeln auf Netzwerkebene, auf einem einzelnen Kanal oder in der Logik eines intelligenten Vertrags gesteuert werden. Im selben Blockchain-Netzwerk können mehrere Identifikationsdienste verschiedener Typen gleichzeitig arbeiten.


Implementierung des Codecodes


Chaincode kann als ein Objekt betrachtet werden, das über Methoden verfügt, die eine bestimmte Geschäftslogik implementieren. Im Gegensatz zum klassischen OOP kann ein Kettencode keine Attributfelder haben. Um mit dem Status zu arbeiten, dessen Speicher von der HLF-Blockchain-Plattform bereitgestellt wird, wird die Ebene ChaincodeStubInterface verwendet , die beim Aufruf der Methoden Init und Invoke übergeben wird. Es bietet die Möglichkeit, Funktionsaufrufargumente zu empfangen und Änderungen am Status der Blockkette vorzunehmen:


 type ChaincodeStubInterface interface { // GetArgs returns the arguments intended for the chaincode Init and Invoke GetArgs() [][]byte // InvokeChaincode locally calls the specified chaincode InvokeChaincode(chaincodeName string, args [][]byte, channel string) pb.Response // GetState returns the value of the specified `key` from the ledger. GetState(key string) ([]byte, error) // PutState puts the specified `key` and `value` into the transaction's writeset as a data-write proposal. PutState(key string, value []byte) error // DelState records the specified `key` to be deleted in the writeset of the transaction proposal. DelState(key string) error // GetStateByRange returns a range iterator over a set of keys in the ledger. GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error) // CreateCompositeKey combines the given `attributes` to form a composite key. CreateCompositeKey(objectType string, attributes []string) (string, error) // GetCreator returns `SignatureHeader.Creator` (eg an identity of the agent (or user) submitting the transaction. GetCreator() ([]byte, error) // and many more methods } 

In dem auf Solidity entwickelten Ethereum-Smart-Vertrag hat jede Methode eine öffentliche Funktion. Im Hyperledger Fabric-Kettencode in den Methoden Init und Invoke mit der Funktion ChaincodeStubInterface . GetArgs () können Sie die Argumente des Funktionsaufrufs in Form eines Arrays von Byte-Arrays abrufen, während das erste Element des Arrays beim Aufruf von Invoke den Namen der Chaincode-Funktion enthält. Weil Der Aufruf einer beliebigen Chaincode-Methode durchläuft die Invoke-Methode. Wir können sagen, dass dies eine Implementierung des Front-Controller-Musters ist.


Wenn wir beispielsweise die Implementierung der Standard-Ethereum-Schnittstelle für das ERC-20- Token in Betracht ziehen, sollte der Smart Contract die folgenden Methoden implementieren:


  • totalSupply ()
  • balanceOf (Adresse _Besitzer)
  • Übertragung (Adresse _to, uint256 _Wert)

und andere. Bei der HLF-Implementierung muss der Funktionscode Invoke in der Lage sein, Fälle zu behandeln, in denen das erste Argument zum Aufrufen von Aufrufen den Namen der erwarteten Methoden enthält (z. B. "totalSupply" oder "balanceOf"). Ein Beispiel für die Implementierung des ERC-20-Standards ist hier zu sehen.


Chaincode-Beispiele


Neben der Dokumentation zu Hyperledger Fabric gibt es einige weitere Beispiele für Kettencodes :



Die Implementierung der Kettencodes in diesen Beispielen ist ziemlich ausführlich und enthält eine Menge sich wiederholender Logik zur Auswahl der aufgerufenen Routing-Funktionen. Überprüfen der Anzahl der Argumente, json marshalling / unmarshalling:


 func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { function, args := stub.GetFunctionAndParameters() fmt.Println("invoke is running " + function) // Handle different functions if function == "initMarble" { //create a new marble return t.initMarble(stub, args) } else if function == "transferMarble" { //change owner of a specific marble return t.transferMarble(stub, args) } else if function == "readMarble" { //read a marble return t.readMarble(stub, args) } else ... 

Eine solche Organisation des Codes führt zu einer Verschlechterung der Lesbarkeit des Codes und möglichen Fehlern wie diesen , wenn Sie einfach vergessen haben, die Eingabedaten zu entfernen. In den Präsentationen zu den HLF-Entwicklungsplänen wird eine Überarbeitung des Ansatzes zur Entwicklung von Kettencodes erwähnt, insbesondere die Einführung von Anmerkungen in Java-Kettencodes usw. Die Pläne beziehen sich jedoch auf die Version, die erst 2019 erwartet wird. Die Erfahrung mit der Entwicklung intelligenter Verträge hat zu dem Schluss geführt, dass das Entwickeln und Testen von Kettencodes einfacher ist, wenn Sie die Grundfunktionen in einer separaten Bibliothek auswählen.


CCKit - eine Bibliothek zum Entwickeln und Testen von Kettencodes


Die CCKit- Bibliothek fasst die Praxis des Entwickelns und Testens von Kettencodes zusammen. Im Rahmen der Entwicklung von Chaincode- Erweiterungen wurde als Beispiel die OpenZeppelin-Erweiterungsbibliothek für Ethereum-Smart-Verträge verwendet. CCKit verwendet die folgenden Architekturlösungen:


Weiterleiten von Anrufen an intelligente Vertragsfunktionen


Routing bezieht sich auf den Algorithmus, mit dem die Anwendung auf eine Clientanforderung reagiert. Dieser Ansatz wird beispielsweise in fast allen http-Frameworks verwendet. Der Router verwendet bestimmte Regeln, um die Anforderung und den Anforderungshandler zu binden. In Bezug auf einen Kettencode dient dies dazu, den Namen der Kettencodefunktion mit der Handlerfunktion zu verknüpfen.


In den neuesten Beispielen für intelligente Verträge, beispielsweise in der Versicherungs-App , wird die Zuordnung zwischen dem Namen der Kettencodefunktion und der Funktion im Golang-Code des Formulars verwendet:


 var bcFunctions = map[string]func(shim.ChaincodeStubInterface, []string) pb.Response{ // Insurance Peer "contract_type_ls": listContractTypes, "contract_type_create": createContractType, ... "theft_claim_process": processTheftClaim, } 

Der CCKit-Router verwendet einen ähnlichen Ansatz wie der http-Router sowie die Möglichkeit, den Anforderungskontext für die Chaincode-Funktion und die Middleware-Funktionen zu verwenden


Der Kontext des Aufrufs der Funktion des Codes


Ähnlich wie der http-Anforderungskontext, der normalerweise Zugriff auf die http-Anforderungsparameter hat, verwendet der CCKit-Router den Kontext des Aufrufs der Smart Contract- Funktion, die eine Abstraktion über shim.ChaincodeStubInterface darstellt . Der Kontext kann das einzige Argument für den Handler der Verkettungsfunktion sein. Über ihn kann der Handler die Argumente des Funktionsaufrufs sowie Zugriff auf Hilfsfunktionen für die Arbeit mit dem Status des intelligenten Vertrags (Status), das Erstellen von Antworten (Antwort) usw. erhalten.


 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 } 

Weil Der Kontext ist eine Schnittstelle, in bestimmten Kettencodes kann er erweitert werden.


Middleware-Funktionen


Funktionen der Zwischenverarbeitung (Middleware) werden vor dem Aufruf des Handlers der Codemethode aufgerufen, haben Zugriff auf den Kontext des Aufrufs der Codemethode und auf die nächste Zwischenfunktion oder direkt auf den Handler der Methode des nächsten (next). Middleware kann verwendet werden für:


  • Konvertieren von Eingabedaten (im folgenden Beispiel sind p.String und p.Struct Middleware)
  • Einschränkungen beim Zugriff auf die Funktion (z. B. Eigentümer. Nur )
  • Abschluss des Anforderungsverarbeitungszyklus
  • Aufruf der nächsten Zwischenverarbeitungsfunktion vom Stapel

Datenstrukturkonvertierung


Die Kettencode-Schnittstelle nimmt an, dass der Eingabe ein Array von Byte-Arrays zugeführt wird, deren Elemente jeweils ein Attribut der Kettencode-Funktion sind. Um zu verhindern, dass aus den Funktionsaufrufargumenten in jedem Handler der Verkettungsfunktion ein manuelles Daten-Marshalling vom Byte-Array zum Golang-Datentyp (int, string, struct, array) erfolgt, werden die erwarteten Datentypen zum Zeitpunkt der Erstellung der Routing-Regel im CCKit-Router festgelegt und der Typ wird automatisch konvertiert . Im folgenden Beispiel erwartet die carGet- Funktion ein Argument vom Typ string, und die carRegister- Funktion erwartet eine CarPayload- Struktur. Das Argument wird auch benannt, wodurch der Handler seinen Wert aus dem Kontext anhand des Namens abrufen kann. Ein Beispiel für einen Handler wird unten angegeben. Protobuf kann auch verwendet werden, um das Verkettungsdatenschema zu beschreiben.


 r.Group(`car`). Query(`List`, cars). // chain code method name is carList Query(`Get`, car, p.String(`id`)). // chain code method name is carGet, method has 1 string argument "id" Invoke(`Register`, carRegister, p.Struct(`car`, &CarPayload{}), // 1 struct argument owner.Only) // allow access to method only for chaincode owner (authority) 

Die automatische Konvertierung (Marshalling) wird auch beim Schreiben von Daten in den Status eines intelligenten Vertrags und beim Erstellen von Ereignissen verwendet (der Golang-Typ wird in ein Array von Bytes serialisiert).


Tools zum Debuggen und Protokollieren von Kettencodes


Zum Debuggen des Codes können Sie die Debug- Erweiterung verwenden, die intelligente Vertragsmethoden implementiert, mit denen Sie das Vorhandensein von Schlüsseln im Status des intelligenten Vertrags überprüfen und den Wert direkt nach Schlüssel lesen / ändern / löschen können.


Für die Protokollierung im Kontext eines Aufrufs einer Chaincode-Funktion kann die Log () -Methode verwendet werden, die eine Instanz des in HLF verwendeten Loggers zurückgibt.


Intelligente Vertragsmethoden Zugriffskontrollmethoden


Im Rahmen der Eigentümererweiterung werden grundlegende Grundelemente zum Speichern von Informationen über den Eigentümer des instanziierten Kettencodes und Zugriffsmodifikatoren (Middleware) für intelligente Vertragsmethoden implementiert.


Intelligente Tools zum Testen von Verträgen


Das Bereitstellen des Blockchain-Netzwerks, das Installieren und Initialisieren von Kettencodes ist eine ziemlich komplizierte Einrichtung und ein langwieriger Vorgang. Die Zeit für die Neuinstallation / Aktualisierung des Smart-Vertragscodes kann durch Verwendung des DEV-Modus des Smart-Vertrags verkürzt werden. Die Aktualisierung des Codes ist jedoch weiterhin langsam.


Das Shim- Paket enthält eine Implementierung von MockStub , die Aufrufe des Codes für den Code umschließt und dessen Betrieb in der HLF-Blockchain-Umgebung simuliert. Mit MockStub erhalten Sie fast sofort Testergebnisse und können die Entwicklungszeit verkürzen. Wenn wir das allgemeine Funktionsschema des Codes in HLF berücksichtigen, ersetzt MockStub im Wesentlichen das SDK, sodass Sie die Funktionen des Codes aufrufen können, und ahmt die Startumgebung des Codes auf dem Host nach.



Der MockStub aus der HLF-Lieferung enthält die Implementierung fast aller Methoden der shim.ChaincodeStubInterface- Schnittstelle. In der aktuellen Version (1.3) fehlen jedoch einige wichtige Methoden wie GetCreator. Weil Kettencode kann diese Methode verwenden, um ein Zertifikat eines Transaktionserstellers für die Zugriffskontrolle zu erhalten. Für eine maximale Abdeckung in Tests ist die Fähigkeit, einen Stub dieser Methode zu haben, wichtig.


Die CCKit-Bibliothek enthält eine erweiterte Version von MockStub , die die Implementierung der fehlenden Methoden sowie Methoden zum Arbeiten mit Ereigniskanälen usw. enthält.


Beispiel für einen Kettencode


Erstellen wir beispielsweise einen einfachen Kettencode zum Speichern von Informationen über zugelassene Autos


Datenmodell


Der Status des Codecodes ist der Schlüsselwertspeicher, in dem der Schlüssel eine Zeichenfolge und der Wert ein Array von Bytes ist. Die grundlegende Praxis besteht darin, jonalisierte Golang-Datenstrukturinstanzen als Werte zu speichern. Um mit Daten im Kettencode zu arbeiten, müssen Sie nach dem Lesen aus dem Status das Byte-Array entfernen.


Um über das Auto aufzuzeichnen, verwenden wir die folgenden Attribute:


  • Kennung (Autonummer)
  • Automodell
  • Informationen zum Fahrzeughalter
  • Informationen zur Datenänderungszeit

 // Car struct for chaincode state type Car struct { Id string Title string Owner string UpdatedAt time.Time // set by chaincode method } 

Um Daten in den Kettencode zu übertragen, erstellen Sie eine separate Struktur, die nur die Felder enthält, die von außerhalb des Kettencodes stammen:


 // CarPayload chaincode method argument type CarPayload struct { Id string Title string Owner string } 

Mit Schlüsseln arbeiten


Datensatzschlüssel in einem intelligenten Vertragsstatus sind eine Zeichenfolge. Es unterstützt auch die Möglichkeit, zusammengesetzte Schlüssel zu erstellen, bei denen Teile des Schlüssels durch ein Null-Byte ( U + 0000 ) getrennt sind.


 func CreateCompositeKey(objectType string, attributes []string) (string, error) 

In CCKit können die Funktionen zum Arbeiten mit dem Status eines intelligenten Vertrags automatisch Schlüssel für Datensätze erstellen, wenn die übertragenen Strukturen die Keyer- Schnittstelle unterstützen


 // Keyer interface for entity containing logic of its key creation type Keyer interface { Key() ([]string, error) } 

Um ein Auto aufzuzeichnen, lautet die Schlüsselgenerierungsfunktion wie folgt:


 const CarEntity = `CAR` // Key for car entry in chaincode state func (c Car) Key() ([]string, error) { return []string{CarEntity, c.Id}, nil } 

Intelligente Vertragsfunktionsdeklaration (Routing)


In der Konstruktormethode des Kettencodes können wir die Funktionen des Kettencodes und ihre Argumente definieren. Der Autokennzeichen enthält 3 Funktionen


  • carList gibt ein Array von Car-Strukturen zurück
  • carGet akzeptiert eine Fahrzeugkennung und gibt eine Fahrzeugstruktur zurück
  • carRegister akzeptiert eine serialisierte Instanz der CarPayload-Struktur und gibt das Registrierungsergebnis zurück. Der Zugriff auf diese Methode ist nur für den Eigentümer des Kettencodes möglich, der mithilfe der Middleware aus dem Eigentümerpaket gespeichert wird

 func New() *router.Chaincode { r := router.New(`cars`) // also initialized logger with "cars" prefix r.Init(invokeInit) r.Group(`car`). Query(`List`, queryCars). // chain code method name is carList Query(`Get`, queryCar, p.String(`id`)). // chain code method name is carGet, method has 1 string argument "id" Invoke(`Register`, invokeCarRegister, p.Struct(`car`, &CarPayload{}), // 1 struct argument owner.Only) // allow access to method only for chaincode owner (authority) return router.NewChaincode(r) } 

Im obigen Beispiel wird die Chaincode- Struktur verwendet, in der die Verarbeitung der Methoden Init und Invoke an den Router delegiert wird:


 package router import ( "github.com/hyperledger/fabric/core/chaincode/shim" "github.com/hyperledger/fabric/protos/peer" ) // Chaincode default chaincode implementation with router type Chaincode struct { router *Group } // NewChaincode new default chaincode implementation func NewChaincode(r *Group) *Chaincode { return &Chaincode{r} } //======== Base methods ==================================== // // Init initializes chain code - sets chaincode "owner" func (cc *Chaincode) Init(stub shim.ChaincodeStubInterface) peer.Response { // delegate handling to router return cc.router.HandleInit(stub) } // Invoke - entry point for chain code invocations func (cc *Chaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response { // delegate handling to router return cc.router.Handle(stub) } 

Die Verwendung eines Routers und der grundlegenden Chaincode-Struktur ermöglicht die Wiederverwendung von Handlerfunktionen. Um beispielsweise Chaincode zu implementieren, ohne den Zugriff auf die carRegister Funktion zu carRegister , reicht carRegister aus, eine neue Konstruktormethode zu erstellen


Implementierung der Funktionen eines Smart Contract


Golang-Funktionen - Es gibt drei Arten von intelligenten Vertragsfunktionshandlern im CCKit- Router:


  • StubHandlerFunc - Die Standard-Handler-Schnittstelle akzeptiert shim.ChaincodeStubInterface und gibt die Standardantwort Peer.Response zurück
  • ContextHandlerFunc - nimmt einen Kontext und gibt peer.Response zurück
  • HandlerFunc - akzeptiert den Kontext, gibt die Schnittstelle und den Fehler zurück. Ein Byte-Array kann zurückgegeben werden oder ein beliebiger Golang-Typ, der automatisch in ein Byte-Array konvertiert wird, basierend darauf, welche Peer.Response erstellt wird. Der Antwortstatus lautet je nach übergebenem Fehler shim.Ok oder shim.Error .

 // StubHandlerFunc acts as raw chaincode invoke method, accepts stub and returns peer.Response StubHandlerFunc func(shim.ChaincodeStubInterface) peer.Response // ContextHandlerFunc use stub context as input parameter ContextHandlerFunc func(Context) peer.Response // HandlerFunc returns result as interface and error, this is converted to peer.Response via response.Create HandlerFunc func(Context) (interface{}, error) 

Argumente der im Router beschriebenen Kettencodefunktionen werden automatisch von Bytearrays in Zieldatentypen (eine Zeichenfolge oder eine CarPayload-Struktur) konvertiert.
Die Kettencodefunktion verwendet Statusmethoden, die das Extrahieren und Speichern von Daten in einen Kettencodestatus vereinfachen, indem automatisch Schlüssel erstellt und die übertragenen Daten in Arrays konvertiert werden Byte (im Bytecode wird ein Array von Bytes geschrieben)


 // car get info chaincode method handler func car(c router.Context) (interface{}, error) { return c.State().Get( // get state entry Key(c.ArgString(`id`)), // by composite key using CarKeyPrefix and car.Id &Car{}) // and unmarshal from []byte to Car struct } // cars car list chaincode method handler func cars(c router.Context) (interface{}, error) { return c.State().List( CarKeyPrefix, // get list of state entries of type CarKeyPrefix &Car{}) // unmarshal from []byte and append to []Car slice } // carRegister car register chaincode method handler func carRegister(c router.Context) (interface{}, error) { // arg name defined in router method definition p := c.Arg(`car`).(CarPayload) t, _ := c.Time() // tx time car := &Car{ // data for chaincode state Id: p.Id, Title: p.Title, Owner: p.Owner, UpdatedAt: t, } return car, // peer.Response payload will be json serialized car data c.State().Insert( //put json serialized data to state Key(car.Id), // create composite key using CarKeyPrefix and car.Id car) } 

Intelligente Vertragstests


- — , . 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.


 //Create chaincode mock cc := testcc.NewMockStub(`cars`, New()) 

Weil cars , .


 // load actor certificates actors, err := identity.ActorsFromPemFile(`SOME_MSP`, map[string]string{ `authority`: `s7techlab.pem`, `someone`: `victor-nosov.pem`}, examplecert.Content) 

BeforeSuite Car authority Init . , Cars Init Init , .


 BeforeSuite(func() { // init chaincode expectcc.ResponseOk(cc.From(actors[`authority`]).Init()) // init chaincode from authority }) 

. , CarRegister , .


 It("Allow authority to add information about car", func() { //invoke chaincode method from authority actor expectcc.ResponseOk(cc.From(actors[`authority`]).Invoke(`carRegister`, Payloads[0])) }) It("Disallow non authority to add information about car", func() { //invoke chaincode method from non authority actor expectcc.ResponseError( cc.From(actors[`someone`]).Invoke(`carRegister`, Payloads[0]), owner.ErrOwnerOnly) // expect "only owner" error }) 

:


 It("Disallow authority to add duplicate information about car", func() { expectcc.ResponseError( cc.From(actors[`authority`]).Invoke(`carRegister`, Payloads[0]), state.ErrKeyAlreadyExists) //expect car id already exists }) 

Fazit


- HLF Go, Java, JavaScript, , , - (Solidity) / -. / .


HLF , , ( .). Hypeledger Fabric , .. .

Source: https://habr.com/ru/post/de426705/


All Articles