Kapitel 4. Entwicklung von Smart Contracts
Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com
In diesem Kapitel lernst du die Entwicklung von Fabric-Smart Contracts kennen, indem du einen einfachen Smart Contract und die Fabric-APIs untersuchst, die zur Implementierung von Fabric-Smart Contracts verwendet werden. Sobald du die Grundlagen der Programmierung eines Smart Contracts und der APIs verstehst, können wir mit Kapitel 5 fortfahren, in dem wir den Inhalt dieses Kapitels auf das Aufrufen von Smart Contracts anwenden werden. Um loszulegen, müssen wir zunächst die Hyperledger Fabric Entwicklungstools herunterladen. Sie ermöglichen einen schnellen Einstieg in die Entwicklung von Fabric-Smart Contracts, indem sie eine komplette Fabric-Laufzeit für zwei Organisationen mit Skripten zum Hoch- und Herunterfahren kapseln.
Wir werden die von Hyperledger zur Verfügung gestellten Binärdateien und Beispielprojekte aus dem Fabric-Projekt verwenden. Diese Binärdateien und Beispielprojekte werden uns helfen, ein Fabric-Testnetzwerk zu starten, und die Beispielprojekte bieten mehrere Beispiel-Smart Contracts, anhand derer du lernen kannst, wie du deine eigenen entwickeln kannst. In diesem Kapitel wird ein Beispiel für einen Smart Contract aus dem Beispielprojekt Fabcar untersucht. Die von uns verwendeten Binärdateien haben auf allen unterstützten Betriebssystemen den gleichen Namen.
Dieses Kapitel wird dir helfen, die folgenden praktischen Ziele zu erreichen:
Einen Fabric Smart Contract mit der Programmiersprache JavaScript schreiben
Einen Fabric Smart Contract installieren und instanziieren
Validierung und Bereinigung von Eingaben und Argumenten in einem Smart Contract
Einfache oder komplexe Abfragen erstellen und ausführen
Mit einer privaten Datensammlung in Fabric arbeiten
Installation der Voraussetzungen und Einrichtung von Hyperledger Fabric
Bevor wir Fabric-Smart Contracts entwickeln können, müssen wir die für den Download von Hyperledger Fabric erforderliche Software herunterladen und installieren. Um Hyperledger Fabric für die Entwicklung von Fabric-Smart Contracts herunterzuladen und einzurichten, führen wir ein Skript aus, das bestimmte Software auf der Plattform benötigt, auf der du entwickelst - Windows, Linux oder Mac. Wir müssen Git, cURL, Node.js, npm, Docker und Docker Compose sowie das Fabric-Installationsskript installieren.
Git
Git wird verwendet, um das fabric-samples Repository von GitHub auf deinen lokalen Rechner zu klonen. Wenn du Git nicht installiert hast, kannst du es von https://git-scm.com/downloads herunterladen . Sobald du es heruntergeladen und installiert hast, überprüfe die Git-Installation mit dem folgenden Befehl:
$ git --version git version 2.26.2
cURL
Wir verwenden cURL, um die Fabric-Binärdateien von aus dem Internet herunterzuladen. Du kannst cURL von https://curl.haxx.se/download.html herunterladen . Wenn du es heruntergeladen und installiert hast, überprüfe die Installation, indem du den folgenden Befehl ausführst:
$ curl -V curl 7.54.0 (x86_64-apple-darwin18.0) libcurl/7.54.0 LibreSSL/2.6.5 zlib/1.2.11 nghttp2/1.24.1 Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp Features: AsynchDNS IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz HTTP2 UnixSockets HTTPS- proxy
Node.js und npm
Wir werden JavaScript für verwenden, um unsere Fabric Smart Contracts zu entwickeln. Fabric verwendet Node.js und npm für die Verarbeitung von Smart Contracts. Die unterstützten Versionen von Node.js sind 10.15.3 und höher, und 12.13.1 und höher. Die unterstützten Versionen von npm sind 6 und höher. Node.js beinhaltet npm bei der Installation. Du kannst Node.js von https://nodejs.org/en/download herunterladen . Du kannst die Installation von Node.js und npm überprüfen, indem du die folgenden Befehle ausführst:
$ node -v v10.15.3 $ npm -v 6.11.2
Docker und Docker Compose
Hyperledger Fabric besteht aus mehreren Komponenten, von denen jede als separater ausführbarer Dienst betrieben wird, so dass Fabric Docker-Images jeder Komponente unterhält. Die Images werden auf der offiziellen Docker Hub Website gehostet. Du brauchst mindestens die Docker-Version 17.06.2-ce. Die neueste Version von Docker findest du unter https://www.docker.com/get-started. Wenn Docker installiert wird, wird auch Docker Compose installiert. Du kannst die Docker-Version überprüfen, indem du den folgenden Befehl ausführst:
$ docker -v Docker version 19.03.13, build 4484c46d9d
Überprüfe dann deine Docker Compose Version, indem du dies ausführst:
$ docker-compose --version docker-compose version 1.27.4, build 40524192
Bevor du fortfährst, starte Docker, denn Docker muss laufen, damit die Installation des Fabric-Installationsskripts abgeschlossen werden kann.
Skript zur Installation von Stoffen
Erstelle das Verzeichnis , das du für die Installation der Fabric-Binärdateien und Beispielprojekte verwenden wirst, und wechsle dorthin. Docker muss laufen, denn das Skript benötigt Docker, um die Fabric-Images herunterzuladen.
Das Skript wird Folgendes tun:
Lade die Fabric-Binärdateien herunter
Klone fabric-samples aus dem GitHub Repo
Lade die Hyperledger Fabric Docker-Images herunter
Hier ist der Befehl zum Ausführen des Skripts:
curl -sSL https://bit.ly/2ysbOFE | bash -s
Aber wir werden diesen Befehl noch nicht ausführen. Zuerst speichern wir die Befehlsausgabe, damit wir sie untersuchen können. Vergewissere dich, dass du dich in dem Verzeichnis befindest, das du für die Installation der Fabric-Binärdateien und der Beispielprojekte erstellt hast, und führe dann den folgenden Befehl aus:
curl -sSL https://bit.ly/2ysbOFE > FabricDevInstall.sh
Jetzt kannst du FabricDevInstall.sh in deinem Lieblingseditor öffnen und das Skript untersuchen, um zu sehen, wie es die Fabric-Samples von GitHub klont, die Fabric-Binärdateien herunterlädt und die Docker-Images herunterlädt. Wenn du die Funktionsweise dieses Skripts verstehst, kann dir das später helfen, wenn du deine Fabric-Entwicklungsumgebung anpassen oder sie für deinen Arbeitsablauf optimieren willst.
Nachdem du das Skript untersucht hast, öffne eine Shell und verwandle FabricDevInstall.sh in eine ausführbare Datei, indem du den folgenden Befehl ausführst:
$ chmod +x FabricDevInstall.sh
Führen wir nun FabricDevInstall.sh mit dem folgenden Befehl aus:
FabricDevInstall.sh
Sobald das Skript ausgeführt wurde, sollte alles fertig sein. Das Verzeichnis fabric-samples, die Docker-Images und Fabric-Binärdateien werden im Verzeichnis fabric-samples/bin installiert. In dem Verzeichnis, in dem du den Befehl ausgeführt hast, sollte es jetzt ein Verzeichnis namens fabric-samples geben. Alles, was wir brauchen, befindet sich in fabric-samples. Zuerst werden wir uns den Fabcar-Beispiel-Smart-Contract ansehen, den du im Verzeichnis fabric-samples findest.
Grundlegende Anforderungen an einen Smart Contract
Der Fabcar-Beispiel-Smart-Contract ist ein gültiges, funktionierendes Beispiel für einen grundlegenden Smart Contract. Wir müssen noch viel mehr hinzufügen, bevor er produktionsreif ist, einschließlich Sicherheit, Fehlermanagement, Berichterstattung, Überwachung und Tests. Wir wollen uns an einige wichtige Punkte aus diesem Beispiel-Smart-Contract erinnern. Gehen wir sie durch:
Contract
Klasse- Smart Contracts erweitern die Klasse
Contract
. Dies ist eine einfache Klasse mit wenigen Funktionen. Wir werden uns diese Klasse später in diesem Kapitel ansehen. - Transaktionskontext
- Alle Funktionen der Smart-Contract-Transaktion übergeben ein Transaktionskontextobjekt als erstes Argument. Dieses Transaktionskontextobjekt ist eine
Context
Klasse. Wenn wir uns die KlasseContract
ansehen, werden wir uns auch diese Klasse ansehen. - Konstrukteur
- Alle Smart Contracts müssen einen Konstruktor haben. Das Konstruktor-Argument ist optional und steht für den Namen des Vertrags. Wenn es nicht angegeben wird, wird der Klassenname verwendet. Wir empfehlen dir, einen eindeutigen Namen zu übergeben und dir das wie einen Namensraum vorzustellen, wie eine umgekehrte Domainnamenstruktur.
- Transaktionsfunktion
- Es kann eine Transaktionsfunktion erstellt werden, um einen Smart Contract zu initialisieren und vor Kundenanfragen aufzurufen. Damit kannst du deinen Smart Contract mit allen erforderlichen Ressourcen einrichten und ausführen. Bei diesen Ressourcen kann es sich um Tabellen oder Karten mit Daten handeln, die für Lookups, Übersetzungen, Dekodierung, Validierung, Anreicherung, Sicherheit und so weiter verwendet werden.
- Weltstaat
- Wir können den Weltstatus auf auf verschiedene Arten abfragen. Die einfache Abfrage ist eine Schlüsselabfrage, und eine Bereichsabfrage liefert eine Menge. Es gibt noch eine weitere Möglichkeit, die Rich Query. Wir sehen uns den Weltstatus in Kapitel 5 an.
putState
- Um Daten in das Hauptbuch zu schreiben, verwenden wir die Funktion
putState
. Sie nimmt als Argumente einkey
undvalue
. Dasvalue
ist ein Byte-Array, so dass das Ledger beliebige Daten speichern kann. In der Regel werden wir das Äquivalent von Geschäftsobjekten speichern, die in Byte-Arrays umgewandelt werden, bevor sie alsvalue
Argument übergeben werden. ChaincodeStub
- Die Klasse
ChaincodeStub
enthält mehrere Funktionen, die zur Interaktion mit dem Ledger und dem Weltzustand verwendet werden. Alle Smart Contracts erhalten eine Implementierung dieser Klasse alsstub
Objekt, das in derContext
Klassenimplementierungctx
enthalten ist und die alle Transaktionsfunktionen als ihr erstes Argument erhalten. - Lese-/Schreibvorgänge
- Eine Aktualisierung in einem Smart Contract wird in drei Schritten ausgeführt: eine Lesetransaktion, eine Aktualisierung der von der Lesetransaktion zurückgegebenen In-Memory-Daten und eine anschließende Schreibtransaktion. Auf diese Weise wird ein neuer Weltzustand für den Schlüssel erzeugt, während die Historie des Schlüssels im unveränderlichen dateibasierten Ledger erhalten bleibt.
Es ist wichtig, sich an diesen Punkt zu erinnern: Du kannst das Hauptbuch nicht aktualisieren (oder in das Hauptbuch schreiben) und das, was du geschrieben hast, in derselben Transaktion zurücklesen. Es spielt keine Rolle, wie viele andere Transaktionsfunktionen du von einer Transaktionsfunktion aus aufrufst. Du musst über den Datenfluss einer Transaktionsanfrage nachdenken. Kunden reichen Transaktionsanfragen bei Peers ein, die die angefragte Transaktion bestätigen (hier wird der Smart Contract ausgeführt); die Bestätigungen mit Lese- und Schreibsätzen werden an die Kunden zurückgeschickt; und die bestätigten Anfragen werden an einen Orderer geschickt, der die Transaktionen ordnet und Blöcke erstellt. Der Auftraggeber sendet die geordneten Anfragen an die Commit-Peers, die die Lese- und Schreibsets validieren, bevor sie die Schreibvorgänge im Ledger festschreiben und den Weltstatus aktualisieren.
In der einfachsten Form ist ein Smart Contract ein Wrapper um ChaincodeStub
, denn Smart Contracts müssen die Schnittstelle nutzen, die durch diese Klasse offengelegt wird, um mit dem Ledger und dem Weltzustand zu interagieren. Das ist ein wichtiger Punkt, an den du dich erinnern solltest. Du solltest die Geschäftslogik in einem modularen Design implementieren und deine Contract
Unterklasse wie eine Datenquelle behandeln. So kannst du deinen Code im Laufe der Zeit weiterentwickeln und die Logik in funktionale Komponenten aufteilen, die gemeinsam genutzt und wiederverwendet werden können. In Kapitel 5 befassen wir uns mit dem Design im Zusammenhang mit der Paketierung und Bereitstellung und in Kapitel 6 mit dem modularen Design und der Implementierung, um Wartung und Tests zu erleichtern.
Mehrere Peers, die Endorsing Peers, werden deine Smart Contracts ausführen. In der heutigen Architektur werden die Smart Contracts hinter einem Gateway platziert, das eine Middleware im Smart Contract SDK ist. Das Gateway empfängt Smart-Contract-Anfragen, verarbeitet sie und sendet sie an einen oder mehrere Peers. Die Peers instanziieren den Kettencode zur Ausführung.
SDK
Fabric bietet ein in Go, Java und Node.js (JavaScript) implementiertes SDK für die Entwicklung von Smart Contracts. Wir interessieren uns für das Hyperledger Fabric Smart Contract Entwicklungs-SDK für Node.js, das fabric-chaincode-node genannt wird. Unter musst du es für die Entwicklung von Smart Contracts nicht herunterladen. Du kannst fabric-chaincode-node von GitHub herunterladen oder klonen.
Das fabric-chaincode-node SDK hat eine Menge zu bieten. Wir interessieren uns für ein paar Komponenten, die für die Entwicklung von Smart Contracts zentral sind. Bei den übrigen Dateien handelt es sich um Low-Level-Schnittstellen, Support-Artefakte, Tools und mehr, die benötigt werden, um die Schnittstelle Contract
mit Node.js zu implementieren. Dieses SDK hilft Entwicklern wie uns, indem es eine High-Level-API bereitstellt, so dass wir schnell lernen und uns auf unsere Smart-Contract-Geschäftslogik und das Design konzentrieren können.
Die erste API, an der wir interessiert sind , ist fabric-contract-api. Sie befindet sich im Unterverzeichnis apis von fabric-chaincode-node. Die andere API, die du siehst, fabric-shim-api, ist die Typdefinition und reine Schnittstelle für die fabric-shim-Bibliothek, die wir uns später in diesem Kapitel ansehen.
Wenn wir unser Smart-Contract-Projekt starten und npm install
ausführen, was wir in Kapitel 5 tun werden, lädt npm fabric-contract-api als Modul aus dem npm Public Repository herunter, ebenso wie fabric-shim. Dieser Download erfolgt, weil wir für die Entwicklung von Hyperledger Fabric Smart Contracts zwei explizite Abhängigkeiten haben. Diese werden in diesem Auszug aus der package.json-Datei des Fabcar-Smart Contracts angezeigt:
"dependencies": { "fabric-contract-api": "^2.0.0", "fabric-shim": "^2.0.0" },
fabric-contract-api und fabric-shim sind die einzigen Module, die wir für die Entwicklung unserer Smart Contracts benötigen. fabric-contract-api enthält die Dateien contract.js und context.js, die die Klassen Contract
und Context
implementieren.
Vertragsklasse
Contract
ist eine einfache Klasse. Neben dem Konstruktor gibt es Hilfsfunktionen, die du überschreiben kannst, um Logik vor und nach Transaktionen zu implementieren:
constructor(name) { this.__isContract = true; if (typeof name === 'undefined' || name === null) { this.name = this.constructor.name; } else { this.name = name.trim(); } logger.info('Creating new Contract', name); }
Die Funktion beforeTransaction
wird aufgerufen, bevor die Funktionen der Vertragstransaktion aufgerufen werden. Du kannst diese Methode außer Kraft setzen, um deine eigene Logik zu implementieren:
async beforeTransaction(ctx) { // default implementation is do nothing }
Die Funktion afterTransaction
wird aufgerufen, nachdem alle Funktionen der Vertragstransaktion aufgerufen wurden. Du kannst diese Methode außer Kraft setzen, um deine eigene Logik zu implementieren:
async afterTransaction(ctx, result) { // default implementation is do nothing }
Die Funktion getName
ist ein Getter , der den Vertragsnamen zurückgibt:
getName() { return this.name; }
Und die Funktion createContext
erstellt einen benutzerdefinierten Transaktionskontext:
createContext() { return new Context(); }
Transaktionskontext
Du kannst einen eigenen Transaktionskontext erstellen, um deine eigenen Objekte zu speichern, auf die deine Funktionen über das ctx
Objekt zugreifen können, das alle Transaktionsfunktionen als erstes Argument erhalten. Hier ist ein Beispiel für die Erstellung eines benutzerdefinierten Kontexts:
const AssetList = require('./assetlist.js'); class MyContext extends Context { constructor() { super(); this.assetList = new AssetList(this); } } class AssetContract extends Contract { constructor() { super('org.my.asset'); } createContext() { return new MyContext(); } }
Mit dem benutzerdefinierten Kontext MyContext
können Transaktionen auf assetList als ctx.assetList zugreifen.
Wie du siehst, ist es ganz einfach, einen einfachen, intelligenten Vertrag zu erstellen. Du importierst Contract
aus fabric-contract-api und erweiterst es. Dann erstellst du einen Konstruktor ohne Argumente und exportierst unseren Vertrag. Das war's.
Kontext Klasse
OK, das ist die Klasse Contract
, aber was über die Klasse Context
? Du hast gerade gelernt, wie man einen benutzerdefinierten Transaktionskontext erstellt, aber was enthält die Klasse Context
? Wie du gelernt hast, erhält jede Transaktionsfunktion ein Context
Objekt namens ctx
als erstes Argument. Das ist der Transaktionskontext. Er enthält zwei wichtige Objekte: stub
und clientIdentity
. Das stub
ist eine Implementierung der Klasse ChaincodeStub
und das clientIdentity
ist eine Implementierung der Klasse ClientIdentity
. Wir besprechen diese Klassen als nächstes.
ChaincodeStub
hat alle Funktionen, die wir brauchen, um mit dem Ledger und dem Weltzustand zu interagieren. Es ist unsere API für den Ledger und den Weltzustand. Sie ist im Modul fabric-shim im Verzeichnis lib enthalten und in der Datei stub.js implementiert. Die beiden wichtigsten Funktionen sind getState
und putState
. Mehrere zusätzliche Funktionen sind verfügbar. Die meisten können in die folgenden Kategorien eingeteilt werden:
Bundeslandbezogen
Abfrage bezogen
Transaktionsbezogen
Private Daten im Zusammenhang mit
Diese vier Gruppen stellen die meisten der Funktionen dar. Die zustandsbezogenen Funktionen werden verwendet, um aus dem Hauptbuch zu lesen und in das Hauptbuch zu schreiben. Sie verwenden oder beinhalten die Verwendung eines Schlüssels.
Die abfragebezogenen Funktionen sind zwei Rich Query Funktionen, eine davon mit Paginierung. Rich-Queries sind String-Queries die in der Datenbank enthalten sind. Um Rich Queries zu nutzen, musst du CouchDB als Datenbank verwenden. Das werden wir in Kapitel 5 tun, wo du lernst, wie du smarte Verträge aufrufst. Eine weitere einzigartige Abfragefunktion ist getHistoryForKey
, die einen Schlüssel nimmt und die Historie für diesen Schlüssel zurückgibt. Damit kannst du Änderungen überprüfen und Transaktionen finden, die fehlgeschlagen sind.
Hyperledger hat fünf transaktionsbezogene Funktionen:
getTxID(): string;
getChannelID(): string;
getCreator(): SerializedIdentity;
getMspID(): string;
getTransient(): Map<string, Uint8Array>;
Verwende getTxID
, um die Transaktions-ID abzurufen, getChannelID
für die ID des Kanals, getCreator
für den Kunden, getMspID
für die Organisation, zu der der Kunde gehört, und getTransient
für private Daten. In den nächsten beiden Kapiteln werden wir jeden dieser Schritte ausführen.
Hyperledger hat neun private datenbezogene Funktionen:
getPrivateData(collection: string, key: string): Promise<Uint8Array>;
getPrivateDataHash(collection: string, key: string): Promise<Uint8Array>;
putPrivateData(collection: string, key: string, value: Uint8Array): Promise<void>;
deletePrivateData(collection: string, key: string): Promise<void>;
setPrivateDataValidationParameter(collection: string, key: string, ep: Uint8Array): Promise<void>;
getPrivateDataValidationParameter(collection: string, key: string): Promise<Uint8Array>;
getPrivateDataByRange(collection: string, startKey: string, endKey: string): Promise<Iterators.StateQueryIterator> & AsyncIterable<Iterators.KV>;
getPrivateDataByPartialCompositeKey(collection: string, objectType: string, attributes: string[]): Promise<Iterators.StateQueryIterator> & AsyncIterable<Iterators.KV>;
getPrivateDataQueryResult(collection: string, query: string): Promise<Iterators.StateQueryIterator> & AsyncIterable<Iterators.KV>;
Diese neun Funktionen für private Daten ermöglichen das Lesen und Schreiben in eine private Datensammlung, das Abrufen eines privaten Datenhashs, das Löschen von privaten Daten, das Festlegen und Abrufen einer Bestätigungsrichtlinie für die Validierung privater Daten und das Abrufen privater Daten über einen Bereich, einen partiellen zusammengesetzten Schlüssel oder eine umfangreiche Abfrage. Wir werden einige dieser Funktionen in den Kapiteln 5 und 6 ausführen, wenn wir private Daten in unseren Smart Contract-Transaktionen verwenden.
Nachdem du nun eine gute Vorstellung davon hast, welche Funktionen über das Objekt stub
zur Verfügung stehen, das wir von dem Objekt ctx
erhalten, schauen wir uns ClientIdentity
an, das andere Objekt im Transaktionskontext.
Das Objekt clientIdentity
enthielt im Transaktionskontext, da ctx.clientIdentity
die Implementierung der Klasse ClientIdentity
ist, die eine kleine Klasse mit nur fünf Funktionen ist:
assertAttributeValue(attrName: string, attrValue: string): boolean;
getAttributeValue(attrName: string): string | null;
getID(): string;
getIDBytes(): Uint8Array;
getMSPID(): string;
Die Funktionen assertAttributeValue
und getAttributeValue
arbeiten mit dem Client-Zertifikat. Mit diesen Funktionen kann eine granulare Sicherheit implementiert werden, indem die Attributwerte des Zertifikats verwendet werden. Die Funktionen getID
und getIDBytes
rufen die Identität des Kunden ab, und getMSPID
wird verwendet, um die Organisation zu ermitteln, der der Kunde angehört. Mit diesen Funktionen kannst du eine Vielzahl von Authentifizierungs- und Autorisierungsmustern umsetzen.
Transaktionsfunktionen
Transaktionsfunktionen sind die Smart-Contract-Funktionen, die Kunden aufrufen. Das sind die Geschäftsfunktionen, die du in deinen Smart Contracts entwirfst und implementierst. Hier ist ein Beispiel für drei Transaktionsfunktionen aus dem Fabcar Smart Contract. Wir werden sie im Abschnitt "Definieren eines Smart Contracts" definieren :
async queryCar(ctx
,carNumber
) async createCar(ctx
,carNumber
,make
,model
,color
,owner
) async queryAllCars(ctx
)
Alle Transaktionsfunktionen erhalten als erstes Argument den Transaktionskontext, das ctx
Objekt. Die Transaktionsfunktionen verwenden dieses Objekt, um auf die Objekte stub
und clientIdentity
zu verweisen - zum Beispiel ctx.stub
und ctx.clientIdentity
. stub
ist eine Instanz von ChaincodeStub
. clientIdentity
ist eine Implementierung von ClientIdentity
und stellt Funktionen zur Verfügung, um die Transaktions-ID, die Client-ID, alle Client-Attribute und die Organisations-ID zu erhalten. Diese Funktionen können für die anwendungs- und transaktionsspezifische Authentifizierung und Autorisierung verwendet werden.
Es ist üblich, dass die meisten Transaktionsfunktionen einen Aufruf des Ledgers oder des Weltzustands enthalten. Die stub
stellt die Funktionen zum Lesen aus dem Weltzustand und zum Schreiben in den Ledger bereit, der den Weltzustand aktualisiert.
Die Art und Weise, wie du deine Transaktionsfunktionen gestaltest, liegt ganz in deiner Hand. Du kannst sie gruppieren, mischen, Bibliotheken erstellen und vieles mehr. Erinnere dich daran, dass bei Transaktionen, die du schreibst, alle vorgesehenen Endorser deine Smart Contracts ausführen müssen.
Befürwortungsrichtlinien entscheiden darüber, ob ein Vorgang bestätigt wird. Eine Bestätigungsrichtlinie kann zum Beispiel festlegen, dass drei von vier Peers die Transaktion bestätigen müssen. Wenn aus irgendeinem Grund weniger als drei Peers die Transaktion bestätigen können, wird die Transaktion nicht bestätigt, d.h. die Daten werden nicht im Ledger verfügbar sein.
Du solltest dich daran erinnern, dass ein Schreibvorgang, auch wenn er fehlschlägt, als ungültig gekennzeichnet und in das Hauptbuch geschrieben wird. Eine ungültige Transaktion ist nicht Teil des Weltzustands.
Als bewährte Methode erfordern Fabric Smart Contracts deterministischen Code. Viele Peers müssen den Code ausführen und alle müssen zum gleichen Ergebnis kommen. Daher müssen die Eingaben immer das gleiche Ergebnis liefern. Es darf keine Rolle spielen, welcher Peer den Code ausführt. Bei gleichen Eingaben sollte der Peer auch die gleichen Ergebnisse liefern. Das sollte unabhängig davon sein, wie oft der Code ausgeführt wird.
Der Code sollte einen Anfang und ein Ende haben. Er sollte nie von dynamischen Daten oder langen zufälligen Ausführungen abhängen. Der Code sollte schnell und effizient sein, mit klaren logischen Abläufen, die nicht zirkulär sind. Zum Beispiel:
async CreateAsset(ctx
,id
,amount
,owner
) { const asset = { ID:id
, Amount:amount
, Owner:owner
}; return ctx.stub.putState(id,Buffer.from( JSON.stringify(asset))); }
Wenn du einen Vermögenswert anlegst, erwarten wir die ID des Vermögenswerts, den Betrag und den Eigentümer als Eingaben.
Argumente validieren und bereinigen
Transaktionen müssen validieren und ihre Argumente bereinigen. Das gilt nicht nur für Smart Contracts. Es ist eine sinnvolle Standardpraxis, wenn du die Integrität und Verfügbarkeit deines Smart Contracts sicherstellen willst.
Nutze bekannte Techniken, um deine Argumente zu überprüfen und Daten zu vermeiden, die deinem Smart Contract schaden könnten. Außerdem musst du deine Argumente bereinigen und die Qualität der Daten, die du erwartest, sicherstellen. Du willst die unnötige Verarbeitung von Daten begrenzen, die später in deiner Logik einen Fehler oder einen nicht abgedeckten Kantenfall verursachen. Hier ist ein Beispiel, das überprüft, ob die Funktion Process
ist; wenn nicht, wird eine Ausnahme geworfen:
func (c *Asset) Invoke(stub shim.ChaincodeStubInterface) pb.Response { function, args := stub.GetFunctionAndParameters() if function == "Process" { return c.Process(stub, args) } return shim.Error("Invalid function name") }
Einfache Zustandsinteraktion (get, put, delete)
Die Smart Contracts der Fabric, die den Kern von bilden, sind Zustandsmaschinen, die sich im Laufe der Zeit weiterentwickeln und eine unveränderliche Historie aller vorherigen Zustände speichern. Die drei wichtigsten stub
Funktionen, die du verwenden wirst, sind die folgenden:
getState(key:string
): Promise<Uint8Array
>; putState(key:string
, value:Uint8Array
): Promise<void>; deleteState(key:string
): Promise<void>;
Die Funktionen von stub
bieten deinen Smart Contracts die Möglichkeit, aus dem World State zu lesen, in den Ledger zu schreiben und aus dem World State zu löschen.
Das Hauptbuch und der Weltzustand sind Key-Value-Datenspeicher. Das hält die Sache einfach und übersichtlich, ermöglicht es aber, umfangreiche Daten im Ledger zu speichern, abzufragen und über den World State, ein Dokument oder eine NoSQL-Datenbank einzusehen. Derzeit werden LevelDB und CouchDB unterstützt. LevelDB ist ein einfacher Key-Value-Datenspeicher , während CouchDB eine umfangreiche und robuste NoSQL-Dokumentendatenbank ist. Das bedeutet, dass du einfache bis komplexe JSON-Datenstrukturen lesen und in das Hauptbuch schreiben und die World State Database nach umfangreichen Daten abfragen kannst. Für Abfragen stehen mehrere Funktionen zur Verfügung.
Erstellen und Ausführen von Abfragen
Smart Contracts müssen oft nachschlagen oder Daten aus dem Weltzustand abfragen, während sie eine Transaktion verarbeiten. Erinnerst du dich an die Aktualisierung unseres Fabcar-Muster-Smart Contracts? Um eine Aktualisierung durchzuführen, muss ein Smart Contract normalerweise das zu aktualisierende Objekt finden und laden. Dann aktualisiert er die In-Memory-Daten und schreibt die aktualisierten Daten in den Ledger - denk an putState
zum Schreiben und getState
zum Lesen.
Im Gegensatz zu einer relationalen Datenbank, in der wir ein Feld aktualisieren können, ohne vorher die Zeile auszuwählen, müssen wir bei Fabric Smart Contracts den Wert eines Schlüssels laden und den Wert aktualisieren. Das kann ein einzelnes Feld in einer sehr großen und komplexen JSON-Datenstruktur sein, oder ein einfaches Objekt mit einem Feld, dem Feld, das wir aktualisieren. Warum ist das so? Weil alle Daten an einen eindeutigen Schlüssel gebunden sind. Wenn der zugehörige Wert eines Schlüssels ein Objekt mit vier Feldern ist, betrachten wir jedes Feld als einen Wert - aber für das Hauptbuch ist das alles nur ein einziges Wertobjekt, das durch einen einzigen eindeutigen Schlüssel identifiziert wird.
Hier sind die verfügbaren stub
Funktionen, die du zum Suchen von Daten verwenden kannst:
getState(key: string): Promise<Uint8Array>;
getStateByRange(startKey: string, endKey: string): Promise<Iterators.StateQueryIterator> & AsyncIterable<Iterators.KV>;
getStateByRangeWithPagination(startKey: string, endKey: string, pageSize: number, bookmark?: string): Promise<StateQueryResponse<Iterators.StateQueryIterator>> & AsyncIterable<Iterators.KV>;
getQueryResult(query: string): Promise<Iterators.StateQueryIterator> & AsyncIterable<Iterators.KV>;
getQueryResultWithPagination(query: string, pageSize: number, bookmark?: string): Promise<StateQueryResponse<Iterators.StateQueryIterator>> & AsyncIterable<Iterators.KV>;
getHistoryForKey(key: string): Promise<Iterators.HistoryQueryIterator> & AsyncIterable<Iterators.KeyModification>;
Wir besprechen diese in Kapitel 5, wenn wir sie in einem Smart Contract verwenden und sie von einem Client aus aufrufen.
Einen Smart Contract definieren
Beginnen wir mit dem einfachen Fabcar Smart Contract. Im Verzeichnis fabric-samples suchst du das Verzeichnis chaincode. Suche im chaincode-Verzeichnis das fabcar-Verzeichnis. Suche im fabcar-Verzeichnis das javascript-Verzeichnis. Wechsle in deiner Shell in dieses Verzeichnis und führe den folgenden Befehl aus:
$ npm install
Dadurch wird das Verzeichnis node_modules erstellt und die abhängigen Module, die in package.json definiert sind, installiert. Wir haben das getan, weil wir auf die Module fabric-contract-api und fabric-shim angewiesen sind. Das sind die beiden Module, die wir bei der Entwicklung von Fabric-Smart Contracts in JavaScript verwenden. Wir werden uns diese Module ansehen, nachdem wir den Fabcar-Smart-Contract untersucht haben.
Schauen wir uns nun den Fabcar-Smart-Contract an. Dieser einfache Smart Contract ist ein großartiges Beispiel, um die Entwicklung von Fabric-Smart Contracts zu lernen, denn er enthält die notwendigen Details, um eine Grundlage zu schaffen, auf der wir zu fortgeschritteneren Smart Contracts übergehen können. Er befindet sich im lib-Verzeichnis im aktuellen Verzeichnis, das das fabric-samples/chaincode/fabcar/javascript-Verzeichnis sein sollte. Öffne fabcar.js in deinem Lieblingseditor; Beispiel 4-1 zeigt den Quellcode.
Beispiel 4-1. fabcar.js
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Contract } = require('fabric-contract-api');
class FabCar extends Contract {
async initLedger(ctx) {
console.info('============= START : Initialize Ledger ===========');
const cars = [
{
color: 'blue',
make: 'Toyota',
model: 'Prius',
owner: 'Tomoko',
},
...
];
for (let i = 0; i < cars.length; i++) {
cars[i].docType = 'car';
await ctx.stub.putState('CAR' + i,
Buffer.from(JSON.stringify(cars[i])));
}
}
async queryCar(ctx, carNumber) {
const carAsBytes = await ctx.stub.getState(
carNumber); // get the car from chaincode state
if (!carAsBytes || carAsBytes.length === 0) {
throw new Error(`${carNumber} does not exist`);
return carAsBytes.toString();
}
async createCar(ctx, carNumber, make, model, color, owner) {
console.info('============= START : Create Car ===========');
const car = {color, docType: 'car',make, model, owner}
await ctx.stub.putState(carNumber, Buffer.from(
JSON.stringify(car)));
}
async queryAllCars(ctx) {
const startKey = '';
const endKey = '';
const allResults = [];
for await (const {key, value} of ctx.stub.getStateByRange(
startKey, endKey)) {
const strValue = Buffer.from(value).toString('utf8');
let record;
try {
record = JSON.parse(strValue);
} catch (err) {
record = strValue;
}
allResults.push({ Key: key, Record: record });
}
return JSON.stringify(allResults);
}
async changeCarOwner(ctx, carNumber, newOwner) {
const carAsBytes = await ctx.stub.getState(carNumber);
if (!carAsBytes || carAsBytes.length === 0) {
throw new Error(`${carNumber} does not exist`);
}
const car = JSON.parse(carAsBytes.toString());
car.owner = newOwner;
await ctx.stub.putState(carNumber, Buffer.from(
JSON.stringify(car)));
}
}
module.exports = FabCar;
Wir beginnen damit, das Modul fabric-contract-api zu importieren.
Alle Fabric Smart Contracts erweitern die Klasse
Contract
. Wir erhalten die KlasseContract
aus dem fabric-contract-api Modul, das wir in Zeile 1 importiert haben.Smart Contracts können Transaktionen verwenden, um sie zu initialisieren, bevor sie Anfragen von Client-Anwendungen bearbeiten. Diese Zeile ist der Anfang der Funktion, die den Smart Contract initialisiert. Alle Smart-Contract-Funktionen erhalten ein Transaktionskontextobjekt als Argument, das per Konvention
ctx
genannt wird.In diesem Beispiel erstellt die Funktion
initLedger
ein Array von Objekten namenscars
. Jedes Array-Objekt enthält Schlüssel-Werte-Paare. Du kannst dir das Array mit den Objekten als Datensätze von Vermögenswerten und die Objekt-Schlüsselwertpaare als Felder vorstellen. Mit dieser Funktion wird ein Array voncar
Objekten vorgeladen, um die Transaktionsfunktionen des Smart Contracts auszuführen.Als Nächstes durchläuft die Funktion
initLedger
das Array voncar
Vermögensobjekten und fügt jedem Objekt ein Feld namensdocType
hinzu und weist jedem Objekt den Stringwertcar
zu.Diese Zeile ist die erste Verwendung des
ctx
Objekts (KlasseContext
), das als erstes Funktionsargument an alle Transaktionsfunktionen der KlasseContract
übergeben wird. Dasctx
Objekt enthält dasstub
Objekt, , das eineChaincodeStub
Klasse ist. Die KlasseChaincodeStub
implementiert eine API für den Zugriff auf das Hauptbuch. Diese Zeile ruft die FunktionputState
auf, die den Schlüssel und den Wert in das Ledger und den Weltzustand schreibt.Hinweis
Hyperledger Fabric implementiert den Blockchain-Ledger in zwei Komponenten: eine dateibasierte Komponente und eine Datenbankkomponente. Die dateibasierte Komponente ist die unveränderliche Datenstruktur auf niedriger Ebene , die den Ledger implementiert, und die Datenbankkomponente zeigt den aktuellen Zustand des dateibasierten Ledgers an. Die Datenbankkomponente wird Weltzustand genannt, weil sie den aktuellen Zustand des Ledgers darstellt. Die dateibasierte Komponente verwaltet das ewige unveränderliche Hauptbuch. Die fabric-contact-api greift auf den Weltstatus zu. Untergeordnete APIs greifen auf das dateibasierte Hauptbuch zu.
Die erste Transaktionsfunktion kommt als nächstes. Wie bereits erwähnt, ist das erste Argument aller Smart-Contract-Transaktionsfunktionen das
ctx
Objekt, das den Transaktionskontext darstellt und eineContext
Klasse ist. Alle anderen Argumente sind optional.Die Funktion
queryCars
ist eine Lesetransaktion. Über das Objektctx
ruft sie die FunktiongetState
vonstub
auf, die aus dem Weltzustand - der Datenbank - liest. Die Funktionstub
ist eine Implementierung der KlasseChaincodeStub
, die wir später behandeln werden. Bei dieser Funktion ist das Argument, dascarNumber
genannt wird, daskey
, das an die FunktiongetState
übergeben wird, die die Weltzustandsdatenbank nachkey
durchsucht und den dazugehörigen Wert zurückgibt. Der Rest der Funktion prüft, ob Daten zurückgegeben wurden. Wenn ja, wandelt sie das zurückgegebene Byte-Array in eine Zeichenkette um und gibt die Zeichenkette zurück, die den Wert des im Weltzustand gespeicherten Schlüssels darstellt. Erinnere dich daran, dass der Weltzustand den aktuellen Zustand des unveränderlichen, dateibasierten Ledgers für jedes im Ledger gespeicherte Schlüssel-Wert-Paar darstellt. Während die Datenbank veränderbar sein kann, ist das dateibasierte Hauptbuch nicht veränderbar. Selbst wenn also ein Schlüssel-Wert-Paar aus der Datenbank oder dem Weltzustand gelöscht wird, befindet sich das Schlüssel-Wert-Paar immer noch im dateibasierten Ledger, wo die gesamte Historie im immerwährenden, unveränderlichen Ledger gespeichert ist.Dann haben wir die zweite Transaktionsfunktion. Sie übergibt die Werte, die zur Erstellung eines neuen
car
Datensatzobjekts erforderlich sind, das wir dem Ledger hinzufügen werden.Mit dem aus den Funktionsargumenten erstellten
car
Datensatz-Objekt rufen wir dieChaincodeStub
API-Funktion auf, die vonstub
implementiert wird undputState
heißt. Diese schreibt diekey
undvalue
in das Hauptbuch und aktualisiert den aktuellen Weltzustand. Die ersten beiden Argumente, die an die FunktionputState
übergeben werden, sind ein Schlüssel bzw. ein Wert. Wir müssen dasvalue
, dascar
Datensatzobjekt, in ein Byte-Array umwandeln, das dieChaincodeStub
APIs benötigen.Die nächste Transaktionsfunktion namens
queryAllCars
ist eine Lesetransaktion und demonstriert eine Bereichsabfrage. Eine Bereichsabfrage wird, wie alle Abfragen, von dem Peer ausgeführt, der die Anfrage erhält. Eine Bereichsabfrage benötigt zwei Argumente: den Anfangsschlüssel und den Endschlüssel. Diese beiden Schlüssel stellen den Anfang und das Ende des Bereichs dar. Alle Schlüssel, die in den Bereich fallen, werden zusammen mit den zugehörigen Werten zurückgegeben. Du kannst für beide Schlüssel eine leere Zeichenkette angeben, um alle Schlüssel und Werte abzufragen.Es wird eine
for
Schleife ausgeführt, die alle Schlüssel und zugehörigen Werte speichert, die von derChaincodeStub
API-FunktiongetStateByRange
zurückgegeben werden.Die letzte Transaktionsfunktion,
changeCarOwner
, kombiniert sowohl Lese- als auch Schreibaufgaben, um den Zustand der Welt zu ändern. Die Geschäftslogik ist hier eine Übertragung des Eigentums. Zusätzlich zum Argumentctx
werden zwei Argumente übergeben: einkey
namenscarNumber
und einvalue
Objekt namensnewOwner
.Als Nächstes müssen wir das Datensatzobjekt aus dem Weltzustand abrufen, das den aktuellen Schlüssel und Wert für diesen Datensatz darstellt. Die
key
istcarNumber
. Wir verwenden es, um dieChaincodeStub
APIgetState
auszuführen. Nachdem wir das aktuellecar
Datensatzobjekt fürcarNumber
abgerufen haben, ändern wir das Feldowner
innewOwner
.Nachdem wir die Ledger-Daten, die den Weltzustand repräsentieren, abgerufen und die abgerufenen Daten aktualisiert haben, aktualisieren wir das Ledger für dieses
car
record object, indem wir dieChaincodeStub
APIputState
ausführen. Dadurch wird ein neueskey
undvalue
in das Ledger geschrieben, das den Weltzustand darstellt. Wenn das Datensatzobjektcar
jetzt abgerufen wird, zeigt das Ledger den neuen Eigentümer erst an, wenn das Datensatzobjekt in das Ledger übertragen wird. Es ist wichtig zu verstehen, dass das Hauptbuch angehängt wird, sobald die Übergabe erfolgt ist, und dass der Datenbankstatus geändert wird (der Weltstatus wird aktualisiert). Du kannst dir das Hauptbuch als einen ständig wachsenden Stapel von Objekten vorstellen, von denen jedes einen eindeutigen Bezeichner, den Schlüssel, hat. Es kann viele Schlüssel mit demselben Wert geben, aber nur einer repräsentiert den aktuellen oder den Weltzustand. Auf diese Weise implementiert die Datenbank die Ansicht des Weltzustands, während das dateibasierte Hauptbuch die unveränderliche Historie aller Schlüssel in Zeitstempelreihenfolge implementiert.Hinweis
Das dateibasierte Hauptbuch speichert alle Schreibvorgänge. Sowohl erfolgreiche als auch fehlgeschlagene Schreibvorgänge sind Teil des dateibasierten unveränderlichen Ledgers. Flags kontrollieren die Gültigkeit von Transaktionen, die im unveränderlichen dateibasierten Ledger gespeichert sind. Dies erleichtert die Prüfung aller eingereichten Schreibvorgänge.
Diese Zeile ist spezifisch für Node.js. Wir besprechen den Export von Smart-Contract-Modulen in Kapitel 5, wenn wir die Ausführung von Smart Contracts, einschließlich Projektstruktur, Paketierung und Bereitstellung, behandeln.
Damit ist dieser einfache Smart Contract fertig. Wir werden ihn nun zusammenfassend besprechen, um die grundlegenden Anforderungen für die Entwicklung eines Smart Contracts aufzuzeigen. Ausgehend von diesem grundlegenden Smart Contract können komplexe Smart-Contract-Anwendungen entworfen und entwickelt werden.
Assets mit Schlüssel-Werte-Paaren definieren
Wenn du Smart Contracts entwirfst ( ), musst du vielleicht in Form von Vermögenswerten denken. Vermögenswerte sind allgemeiner Natur und können viele Dinge darstellen, darunter materielle und immaterielle Objekte. Das können Maschinenteile, Hundefutter, Währungen oder grüne Derivate sein. Wir verwenden Name-Wert-Paare oder Schlüssel-Wert-Paare, je nachdem, wie du darüber denken willst, um unsere Datenstrukturen zu erstellen. Hier ist ein Beispiel, das wir besprechen können:
const assets = [ { color: 'blue', make: 'Honda', model: 'Accord', owner: 'Jones', }, { color: 'red', make: 'Ford', model: 'Mustang', owner: 'Smith', }, ]; for (let i = 0; i < assets.length; i++) { assets[i].docType = 'asset'; await ctx.stub.putState('ASSET' + i, Buffer.from(JSON.stringify(assets[i]))); }
Das haben wir schon im Fabcar-Beispiel gesehen. Es ist ein gutes Beispiel für eine grundlegende Verarbeitung, die du je nach Anwendungsfall ausbauen kannst. In diesem Beispiel wird ein Array von Objekten erstellt, die Assets darstellen. Die Schlüssel-Wert-Paare definieren die Attribute oder Merkmale jedes Vermögenswertes. Das Array fungiert als eine einfache Datenbank für Vermögenswerte. Jeder Vermögenswert wird in das Hauptbuch geschrieben, indem ctx.stub.putState
aufgerufen wird, das einen Schlüssel und einen Wert benötigt. Der Wert muss ein Byte-Array sein, also wandeln wir das JSON-Objekt in einen String um und konvertieren dann den String in das Byte-Array. Du wirst dies oft tun und möchtest es vielleicht vereinfachen und ein Dienstprogramm oder eine Bibliothek erstellen. Dieser spezielle Code wurde zur Initialisierung des Smart Contracts verwendet.
Wir können Vermögenswerte auch mithilfe einer Smart-Contract-Transaktion definieren. Die nachfolgend gezeigte Transaktionsfunktion createAsset
veranschaulicht, wie einfach es ist, einen Vermögenswert zu erstellen und ihn in das Hauptbuch zu schreiben. Diese Funktion wird von einem Client aufgerufen. Der Client kann ein Benutzer oder ein Prozess sein. Wichtig ist, dass du dich daran erinnerst, dass der Vermögenswert erst dann verfügbar ist, wenn die Transaktion in das Hauptbuch übertragen wurde. Du kannst also nicht eine Reihe von Vermögenswerten in den Ledger schreiben und später in deinem Smart Contract erwarten, dass du ihre Daten lesen und verwenden kannst, um die Verarbeitung fortzusetzen. Über diesen unverbundenen Zustand solltest du nachdenken, wenn du mit dem Design und dem Brainstorming beginnst. Hier ist die createAsset
Transaktionsfunktion:
async createAsset(ctx, assetNumber, make, model, color, owner) { const asset = { color, docType: 'asset', make, model, owner, }; await ctx.stub.putState(assetNumber, Buffer.from(JSON.stringify(asset))); }
Private Daten sammeln
Die private Datensammlung(PDC) ist eine Partition der Ledger-Daten, die einer Organisation gehört, die private Daten speichert und die Daten vor anderen Organisationen in diesem Kanal geheim hält. Dazu gehören private Daten und der Hash-Wert der privaten Daten. Kapitel 9 enthält weitere Einzelheiten. Die Notwendigkeit, bestimmte Daten privat zu halten, ist wichtig für die Entwicklung von Smart Contracts. Viele Smart Contracts müssen den Anforderungen an Datenschutz und Sicherheit entsprechen. Fabric unterstützt private Daten für Transaktionsfunktionen und Smart Contracts.
Die privaten Daten können geteilt oder isoliert und sicher aufbewahrt werden. Wir können die privaten Daten ablaufen lassen, nachdem eine bestimmte Anzahl von Blöcken erstellt wurde oder bei Bedarf. Die Daten in den PDCs bleiben von den öffentlichen Daten getrennt, und die PDCs sind lokal und geschützt. Der Weltzustand kann in Verbindung mit PDCs durch die Verwendung von Hashes und öffentlichen Schlüsseln genutzt werden.
In Tabelle 4-1 sind verschiedene Funktionen aufgeführt, die unter stub
zur Verfügung stehen.
API | Hinweis |
---|---|
getPrivateData(collection: |
Gibt die Endorsement-Policy aus dem Sammlungsnamen und dem angegebenen Schlüssel zurück. |
putPrivateData(collection: |
Legt den Sammlungsnamen sowie den angegebenen Schlüssel und Wert in die private writeSet der Transaktion. |
deletePrivateData(collection: |
Löscht die Endorsement-Policy durch Angabe des Sammlungsnamens und des Schlüssels der privaten Datenvariablen. |
setPrivateDataValidationParameter(collection: |
Legt die Endorsement-Richtlinie fest, indem er den Namen der Sammlung und den Schlüssel der privaten Datenvariablen angibt. |
getPrivateDataValidationParameter(collection: |
Liefert die Endorsement-Policy durch Angabe des Sammlungsnamens und des Schlüssels der privaten Datenvariablen zurück. |
getPrivateDataByRange(collection: |
Gibt die Endorsement-Policy aus dem Sammlungsnamen und dem Schlüssel der privaten Datenvariablen zurück. |
getPrivateDataByPartialCompositeKey(collection: |
Fragt die Endorsement-Policy in einem bestimmten Sammlungsnamen und einem bestimmten zusammengesetzten Teilschlüssel ab. |
getPrivateDataQueryResult(collection: |
Führt eine Rich Query gegen eine bestimmte private Sammlung durch. Sie wird für State-Datenbanken unterstützt, die eine Rich Query ausführen können (z. B. CouchDB). |
Die Verwendung privater Daten kann knifflig sein, und es gibt Muster für die Verwendung unter verschiedenen Umständen. Wir werden die meisten dieser Funktionen in Kapitel 5 behandeln, wenn wir Funktionen für private Daten implementieren, um sie aufzurufen, und erneut in Kapitel 6, wenn wir sie bei der Wartung und beim Testen verwenden.
Zugriffskontrolle auf Basis von Attributen festlegen
Letztendlich brauchst du eine Möglichkeit, um eine granulare Authentifizierung und Autorisierung zu implementieren. Kunden haben eine Identität, die den Zugang kontrolliert. Die Identitäten müssen zu autorisierten Organisationen gehören, und Organisationen gehören zu Kanälen, die Chaincode hosten. Ein Zertifikat stellt die Identität des Kunden dar. Es unterstützt Attribute, die zur Umsetzung von Transaktionen und Authentifizierungs- und Autorisierungsrichtlinien auf Smart-Contract-Ebene verwendet werden können.
Du greifst auf diese Informationen über das Objekt clientIdentity
zu, das im Transaktionskontext enthalten ist. Dieses Objekt hat zwei Funktionen, die sich auf die Attributwerte beziehen:
assertAttributeValue(attrName:string
, attrValue:string
):boolean
; getAttributeValue(attrName:string
):string
| null;
Verwende assertAttributeValue
, um das Vorhandensein eines Attributs zu prüfen, und verwende getAttributeValue
, um ein bestimmtes Attribut abzurufen. Es ist eine gute Praxis, das Attribut zu bestätigen, bevor du es abrufst. In den Kapiteln 5 und 6 werden wir Attribute für Sicherheits- und andere Zwecke verwenden.
Initialisiere den Ledger-Status
Die Initialisierung des Ledgers ist oft eine notwendige Aufgabe. Das folgende Beispiel aus dem Fabcar-Code, den wir uns bereits angesehen haben, zeigt, wie du den Zustand deines Smart Contracts initialisierst. Du initialisierst ihn direkt nach dem Commit. Danach kannst du mit dem Einreichen von Transaktionen und dem Abfragen von Ledger-Daten beginnen, indem du Smart-Contract-Methoden aufrufst.
Du erstellst eine Funktion, die du aufrufst, um deine Initialisierung durchzuführen; hier heißt sie initLedger
. In der Funktion initLedger
kannst du das tun, was du für die Initialisierung brauchst. In diesem Beispiel erstellen wir ein Array mit Geschäftsobjekten, durchlaufen das Array cars
und fügen dann jedem car
Objekt im Array cars
ein zusätzliches Attribut docType
hinzu. Hier ist die initLedger
Logik:
async initLedger(ctx) { const cars = [ { color: 'blue', make: 'Toyota', model: 'Prius', owner: 'Tomoko', }, . . . { color: 'brown', make: 'Holden', model: 'Barina', owner: 'Shotaro', }, ]; for (let i = 0; i < cars.length; i++) { cars[i].docType = 'car'; await ctx.stub.putState('CAR' + i, Buffer.from(JSON.stringify(cars[i]))); console.info('Added <--> ', cars[i]); } }
Die Funktion initLedger
schreibt die Array-Objekte mit Hilfe der Funktion putState
in den Ledger. Um die Funktion initLedger
auszuführen, müssen wir den Smart Contract aufrufen. Wir können den Befehl peer
CLI invoke
verwenden. Schauen wir uns an, wie wir initLedger
über den Befehl invoke
aufrufen können.
Kettencode aufrufen init
Um eine Transaktionsfunktion auf unserem Smart Contract auszuführen, können wir den Befehl invoke
verwenden, der von der Peer Binary bereitgestellt wird. Dieses Binary bietet viele Befehle, von denen du einige in den Kapiteln 5 und 6 kennenlernen wirst. Hier verwenden wir ihn, um unsere initLedger
Funktion aufzurufen:
Hinweis
Wir haben den folgenden Befehl zur besseren Lesbarkeit auf mehrere Zeilen gedruckt. Wenn du den Befehl eingibst, muss er in einer Zeile stehen, sonst schlägt die Ausführung fehl.
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls true --cafile /OReilly/fabric-samples/test-network/organizations/ordererOrganizations /example.com/orderers/orderer.example.com/msp/tlscacerts/tls/ ca.example.com-cert.pem -C mychannel -n fabcar --peerAddresses localhost:7051 --tlsRootCertFiles /OReilly/fabric-samples/test-network/organizations /peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --isInit -c '{"function":"initLedger","Args":[]}' [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200
Der Befehl invoke
führt das Befehlsobjekt aus, das dem Befehlsargument-Flag -c
folgt. Das Befehlsobjekt gibt die auszuführende Funktion und alle Funktionsargumente an. Hier führen wir die Funktion initLedger
aus, und es gibt keine Funktionsargumente.
Wir können die Ergebnisse der Funktion initLedger
testen. Wir erwarten, dass sie den Inhalt des Arrays zurückgibt, das in das Hauptbuch geschrieben wurde. Wir werden den Befehl query
verwenden; schauen wir uns an, wie wir Ledger-Daten abfragen können.
Kettencode-Abfrage
Mit dem Befehl query
des Peers, können wir eine unserer Smart Contract Abfragefunktionen ausführen. In diesem Fall setzen wir das -c
Befehlsflag, um queryAllCars
auszuführen:
peer chaincode query -C mychannel -n fabcar -c '{"Args":["queryAllCars"]}'
Hier ist die Rückgabe:
[{"Key":"CAR0","Record":{"color":"blue","docType":"car", "make":"Toyota","model":"Prius","owner":"Tomoko"}}, . . . {"Key":"CAR9","Record":{"color":"brown","docType":"car","make":"Holden", "model":"Barina","owner":"Shotaro"}}]
Das Ergebnis zeigt, dass die Funktion initLedger
ausgeführt wurde und unser Smart Contract initialisiert ist.
Installieren und Instanziieren eines Smart Contracts
In Vorbereitung auf Kapitel 5 gehen wir auf durch, was wir tun müssen, wenn wir unseren Smart Contract fertig kodiert haben. In diesem Abschnitt werden mehrere Schritte erläutert, die wir durchführen müssen, um unseren Smart Contract entweder über die Kommandozeile oder über einen Smart Contract Client aufrufen zu können. Diese Schritte sind wie folgt:
Verpacke den Kettencode.
Installiere den Kettencode.
Frag die Anlage ab.
Genehmige das Paket.
Überprüfe die Commit-Bereitschaft.
Bestätige die Definition des Kettencodes.
Frag ab, ob der Kettencode festgelegt ist.
Initialisiere den Vertrag.
Führe eine Abfrage aus.
In Kapitel 5 werden diese Schritte ausführlicher behandelt. Sie enthalten Beispiel-Befehlszeilencode, und einige haben eine Ausgabe. Die folgenden peer
Befehle können in der Hyperledger Fabric Dokumentation nachgeschlagen werden .
Den Kettencode verpacken
Als Erstes müssen wir unseren Code verpacken. Wie du aus dem folgenden Befehl ersehen kannst, verwenden wir die peer
CLI, um diesen Schritt und alle anderen Schritte durchzuführen.
Um unseren Smart Contract vorzubereiten, verwenden wir den folgenden peer
Paketbefehl:
peer lifecycle chaincode package fabcar.tar.gz \ --path ../chaincode/fabcar/javascript/ \ --lang node \ --label fabcar_1
Sobald dieser Befehl abgeschlossen ist, haben wir eine tar.gz-Datei mit unserem Smart Contract. Als Nächstes müssen wir dieses Paket installieren.
Installiere den Kettencode
Nachdem wir unseren intelligenten Vertrag verpackt haben, können wir ihn installieren. Wenn mehrere Organisationen zusammenarbeiten, müssen nicht alle Organisationen ihre Smart Contracts separat verpacken. Ein einziges Smart-Contract-Paket kann von allen Organisationen genutzt werden. Sobald eine Organisation das Paket erhalten hat, wird es bei den anderen Organisationen installiert, die den Vertrag unterstützen. In Kapitel 9 wird dies ausführlicher beschrieben.
Hier ist der Installationsbefehl, der eine erfolgreiche Ausgabemeldung anzeigt:
peer lifecycle chaincode install fabcar.tar.gz [cli.lifecycle.chaincode] submitInstallProposal -> INFO 001 Installed
Sobald der Vertrag installiert ist, solltest du ihn überprüfen.
Abfrage der Installation
Du kannst den folgenden Befehl ausführen, um die Details des zuletzt installierten Kettencodes zu erhalten:
peer lifecycle chaincode queryinstalled Installed chaincodes on peer: Package ID: fabcar_1:5a00a40697…330bf5de39, Label: fabcar_1
Abhängig von der Anzahl der installierten Pakete erhältst du möglicherweise viele Paket-IDs. Du kannst ein Skript verwenden, um die Ausgabe zu filtern, wenn du eine Aufgabe automatisieren musst, die davon abhängt, ob ein bestimmtes Paket installiert oder nicht installiert ist. Sobald ein Chaincode-Paket installiert ist, muss es genehmigt werden.
Genehmige das Paket
Nach der Installation eines Pakets muss eine Organisation es genehmigen, bevor es übertragen und zugänglich gemacht werden kann. Dieser Befehl hat eine Menge Parameter. Der für unsere Zwecke interessanteste ist –package-id
. Wir können ihn aus der Ausgabe des vorangegangenen queryinstalled
Befehls entnehmen. package-id
wird als Bezeichner für das Chaincode-Installationspaket verwendet:
peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls true --cafile /OReilly/fabric-samples/test-network/organizations/ordererOrganizations /example.com/orderers/orderer.example.com/msp/tlscacerts/ tlsca.example.com-cert.pem --channelID mychannel --name fabcar --version 1 --init-required --package-id fabcar_1:5a00a406972168ac5856857b5867f51d5244208b876206b7e0e418330bf5de39 --sequence 1
Sobald der Befehl approve abgeschlossen ist, können wir feststellen, ob wir die Daten übertragen können. Wir verwenden den Befehl checkcommitreadiness
.
Commit-Bereitschaft prüfen
Eine bestimmte Anzahl von Organisationen muss zustimmen, bevor das Kettencode-Paket freigegeben werden kann. Die Anzahl hängt von der Richtlinie ab, die verlangen kann, dass alle Organisationen oder eine Teilmenge von Organisationen zustimmen. Im Idealfall sollen alle Organisationen zustimmen. Mit dem Befehl checkcommitreadiness
können wir feststellen, ob wir das Paket übertragen können. In diesem Fall können wir es nicht, weil Org2
noch nicht genehmigt hat. Sobald dies der Fall ist, wird dieser Befehl true
für Org1
und true
für Org2
anzeigen:
peer lifecycle chaincode checkcommitreadiness --channelID mychannel --name fabcar --version 1 --sequence 1 --output json --init-required { "approvals": { "Org1MSP": true, "Org2MSP": false } }
In einer Hyperledger-Konfiguration können wir verschiedene Arten von Life-Cycle-Endorsement-Richtlinien definieren. Die Standardeinstellung ist MAJORITY Endorsement
. Dies erfordert, dass eine Mehrheit der Peers eine Chaincode-Transaktion für die Validierung und Ausführung im Channel befürwortet und die Transaktion an das Ledger überträgt. In Kapitel 9 wird dies ausführlicher behandelt. Sobald alle Genehmigungen stimmen, können wir das Kettencode-Paket übertragen.
Die Kettencode-Definition festschreiben
Sobald alle Organisationen oder Untergruppen der Organisation die genannten Richtlinien erfüllen, kann der Kettencode in das Hauptbuch übertragen werden. Um den Kettencode zu übertragen, verwenden wir den hier gezeigten Befehl commit:
peer lifecycle chaincode commit -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls true --cafile /OReilly/fabric-samples/test-network/organizations/ordererOrganizations /example.com/orderers/orderer.example.com/msp/tlscacerts/ tlsca.example.com-cert.pem --channelID mychannel --name fabcar --peerAddresses localhost:7051 --tlsRootCertFiles /OReilly/fabric-samples/test-network/organizations /peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --version 1 --sequence 1 --init-required
Hier ist die Ausgabe, nachdem der Befehl ausgeführt wurde:
[chaincodeCmd] ClientWait -> INFO 001 txid [ef59101c320469be3242daa9ebe262771fc8cc8bd5cd0854c6424e1d2a0c61c2] committed with status (VALID) at localhost:9051
Als Nächstes können wir prüfen, ob der Kettencode mit querycommitted
festgelegt ist.
Abfrage, ob der Kettencode festgeschrieben ist
Chaincode muss festgeschrieben sein, bevor er aufgerufen werden kann. Um herauszufinden, ob Chaincode aktiviert ist, verwende den Befehl querycommitted
chaincode:
peer lifecycle chaincode querycommitted --channelID mychannel --name fabcar
Hier ist die Ausgabe:
Committed chaincode definition for chaincode 'fabcar' on channel 'mychannel': Version: 1, Sequence: 1, Endorsement Plugin: escc, Validation Plugin: vscc, Approvals: [Org1MSP: true, Org2MSP: true]
Die Ausführung des Befehls querycommitted
zeigt uns, dass unser Chaincode fertig ist. Dieser Chaincode enthält einen Smart Contract, der initialisiert werden muss. Um ihn zu initialisieren, rufen wir ihn auf.
Den Vertrag initialisieren
Endlich können wir unseren Smart Contract initialisieren, denn der Chaincode ist genehmigt und bestätigt. Wir können den Befehl invoke
verwenden, um Smart-Contract-Transaktionen auszuführen:
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls true --cafile /OReilly/fabric-samples/test-network/organizations/ordererOrganizations /example.com/orderers/orderer.example.com/msp/tlscacerts/ tlsca.example.com-cert.pem -C mychannel -n fabcar --peerAddresses localhost:7051 --tlsRootCertFiles /OReilly/fabric-samples/test- network/organizations/peerOrganizations/org1.example.com/peers /peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles /OReilly/fabric-samples/test-network/organizations /peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt --isInit -c '{"function":"initLedger","Args":[]}'
Nach der Ausführung des Befehls invoke
sehen wir die folgende Ausgabe; sie gibt einen 200-Antwortstatus zurück, wenn der Aufruf von chaincode erfolgreich war:
[chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200
Am Ende des invoke
Befehls sehen wir das -c
Befehlsflag und das Befehlsobjekt, das den Funktionsschlüssel und den Wert initLedger
ohne Argumente enthält. Das bedeutet, dass die Smart-Contract-Transaktionsfunktion initLedger
ausgeführt wird. Die Ausgabe zeigt ein erfolgreiches Ergebnis. Unser Smart Contract ist nun initialisiert und bereit für Kunden. Wir können unseren Smart Contract jetzt testen, indem wir eine Abfrage ausführen.
Ausführen einer Abfrage
Wir haben die Schritte durchlaufen, um dein Smart-Contract-Quellcode-Projekt zu übernehmen und es zu instanziieren. Wir können es testen, indem wir eine Abfrage wie diese ausführen:
peer chaincode query -C mychannel -n fabcar -c '{"Args":["queryAllCars"]}'
Hier ist die Ausgabe nach dem Ausführen des Befehls queryAllCars
:
[{"Key":"CAR0","Record":{"color":"blue","docType":"car", "make":"Toyota","model":"Prius","owner":"Tomoko"}}, . . . {"Key":"CAR9","Record":{"color":"brown","docType":"car", "make":"Holden","model":"Barina","owner":"Shotaro"}}]
Diese Abfrage führt die Smart-Contract-Transaktionsfunktion namens queryAllCars
aus. Schreibvorgänge werden übertragen und erfordern daher eine Bestätigung, an der mehrere Peers beteiligt sind. Eine Abfrage sollte nicht mehr als einen Peer mit der Ausführung beauftragen müssen. Genau das erledigt der clientseitige Code für uns. Dieses Beispiel zeigt, wie eine Transaktionsfunktion eine ChaincodeStub
Funktion umhüllt, in diesem Fall eine rangeQuery
abstrahiert als queryAllCars
.
Zusammenfassung
Als Vorbereitung auf die Kapitel 5 und 6 haben wir zunächst unsere Hyperledger Fabric-Entwicklungsumgebung eingerichtet und sie genutzt, um einen einfachen, aber vollständigen Smart Contract namens Fabcar zu erkunden und zu untersuchen. Der Code, den wir für Fabric-Smart Contracts schreiben, hängt von den APIs ab, die von den SDKs bereitgestellt werden. Wir haben den Fabcar-Code behandelt, weil er klein und einfach zu erlernen ist. So konnten wir uns auf den Code von Smart Contracts, die verwendeten Klassen und Schnittstellen und die Fabric Smart Contract APIs konzentrieren, auf die wir angewiesen sind.
Fabric-Smart-Contract-SDKs gibt es für JavaScript, Java und Go, und weitere werden folgen. Wir haben das JavaScript Fabric-Smart-Contract-SDK für Node.js verwendet. Damit konnten wir die fabric-contract-api und die wichtigsten Klassen und Objekte kennenlernen, die wir für die Entwicklung von Fabric-Smart Contracts brauchen.
Mit dem Wissen über die API haben wir uns damit beschäftigt, wie man einen Smart Contract erstellt und was Smart-Contract-Transaktionsfunktionen sind. Funktionen führen unsere Smart-Contract-Transaktionen aus. Deshalb war es wichtig, einige wichtige Themen wie die Validierung und Bereinigung von Funktionsargumenten, die Initialisierung von Smart Contracts und die Interaktion mit dem Ledger vorzustellen. Die Fabric Contract API bietet die Schnittstelle zum Ledger, auf den du in unseren Smart Contracts über den Transaktionskontext, den jede Transaktion erhält, zugreifen kannst. Es gab viel zu behandeln, aber wir haben versucht, es einfach zu halten und dir dennoch die fabric-contract-api näher zu bringen, die die Schnittstellen enthält, die du brauchst, um robuste Smart Contracts zu entwickeln und zu implementieren.
Sobald der Code für den Smart Contract geschrieben ist, müssen wir ihn verpacken und im Fabric-Netzwerk bereitstellen. Dazu sind mehrere Schritte erforderlich. Wir haben sie Schritt für Schritt durchlaufen. Es ist wichtig, diese Schritte zu kennen, um unsere Smart Contracts vom Quellcode zum instanziierten Kettencode zu bringen. Wir können nur instanziierten Code ausführen.
In den Kapiteln 5 und 6 verpacken und instanziieren wir die von uns erstellten Smart Contracts, indem wir das in diesem Kapitel gelernte Wissen anwenden. Jetzt können wir mit Kapitel 5 fortfahren und uns auf den Aufruf von Smart Contracts konzentrieren.
Get Hands-On Smart Contract Entwicklung mit Hyperledger Fabric V2 now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.