Quorum Blockchain: Integration in Java-Code

Hallo Habr!


Für diejenigen, die sich für das Thema Blockchain interessieren, ist es kein Geheimnis, dass es neben öffentlichen Blockchains wie Ethereum , Bitcoin , Zcash usw. auch ihre "privaten (privaten)" "Brüder" gibt, die in gewisser Weise besser sind als öffentliche Netzwerke. aber in etwas verlieren sie gegen sie. Unter den bekanntesten Netzwerken können Sie Quorum (Anbieter - JP Morgan Chase ), Pantheon (Anbieter - PegaSys ) und Hyperledger (verwaltet von The Linux Foundation ) nennen. Trotz der Tatsache, dass es viele öffentliche Entscheidungen gibt, ist das Geschäft zunehmend an privaten Blockchains interessiert, da diese das erforderliche Maß an Datenschutz bieten können, Transaktionen schneller sind und so weiter. Die Unterschiede zwischen privaten und öffentlichen Blockchains sowie deren Vor- und Nachteile sind nicht Gegenstand dieses Artikels. Wenn Sie daran interessiert sind, darüber zu lesen, ist dies beispielsweise ein solcher Artikel über Medium .


In diesem Artikel möchte ich Ihnen erklären, wie Sie die Quorum- Blockchain verwenden können, um Ihre Anwendungen mit Unterstützung für private und öffentliche Transaktionen zu entwickeln. Um die Funktionen zu demonstrieren, schreiben wir eine kleine Java / Spring- Anwendung, die Anforderungen zum Bereitstellen (Bereitstellen) intelligenter Verträge, Ausführen von Transaktionen und Lesen von Daten aus einem intelligenten Vertrag akzeptiert. Hier ist der Technologie-Stack, der im Artikel verwendet wird:



Einige allgemeine Informationen zum Quorum


Quorum ist ein Projekt mit Open Source Code auf GitHub , dessen Zweck darin besteht, eine Blockchain bereitzustellen, die es ermöglicht, Transaktionen nicht nur öffentlich, sondern auch im privaten Modus durchzuführen. Aus technischer Sicht ist Quorum ein Ethereum- Upgrade und verfügt über einen eigenen modifizierten Geth- Client, um private Transaktionen durchführen zu können.


Eine wichtige Ergänzung sind auch Enklavendienste , die für die Speicherung, Verschlüsselung und Verteilung privater Transaktionen untereinander verantwortlich sind. Jetzt gibt es 2 solcher Enklavendienste :


  1. Constellation - geschrieben in Haskell, der ersten Version der Enklave , aber jetzt entwickelt sie sich nicht mehr und wird höchstwahrscheinlich in Zukunft zugunsten einer neuen Version aufgegeben.
  2. Tessera - ein neuer, in Java geschriebener Dienst, der von Entwicklern von JP Morgan Chase unterstützt wird, bietet mehr Optionen für die Integration in die Datenbank und die Verwaltung vertraulicher Informationen (zum Beispiel gibt es eine Option für die Integration in HashiCorp Vault zur Verwaltung von Geheimnissen).

Bei Transaktionen hat sich aus Sicht der Schnittstelle des gewöhnlichen Ethereum nicht viel geändert (und das ist gut so). Um eine private Transaktion zu senden, müssen Sie zusätzlich zu den üblichen Informationen über die Transaktion auch den Parameter privateFor angeben - dies ist das Zeilenarray , und diese Zeilen sind öffentliche Schlüssel des Enklavenknotens . Mit diesen Schlüsseln werden Nutzlasttransaktionen verschlüsselt und die Nutzlast auf die Tessera- Knoten innerhalb der Blockchain verteilt.


Um einen tieferen Einblick in Quorum zu erhalten , wie es funktioniert und wie das Blockchain-Netzwerk aufgebaut wird, finden Sie es auf der offiziellen Website (einen Link zur Dokumentation sowie einen Link zum Tutorial zum Starten einer Test-Blockchain werde ich am Ende des Artikels hinterlassen).


Java-Anwendungsentwicklung


Als Beispiel werde ich eine kleine in Java / Spring geschriebene RESTful-API mit Gradle als Build- und Abhängigkeitsmanagement-Tool zeigen, das den Smart-Vertrag in die Blockchain lädt, die Funktion zum Ändern des Vertragsstatus ausführt und den Status aus dem Smart-Vertrag liest.


Bevor ich mit der Entwicklung selbst beginne, muss ich etwas klarstellen. Trotz der Tatsache, dass Quorum offiziell zwei Transaktionsoptionen hat, ziehe ich es vor, sie in drei Typen zu unterteilen:


  1. Öffentliche Transaktionen - Transaktionen sind für alle Netzwerkteilnehmer (einschließlich Nutzdaten ) vollständig sichtbar. Der Enklavenknoten nimmt weder an der Verarbeitung noch an der Speicherung der Transaktion teil. Öffentliche Transaktionen im Quorum unterscheiden sich nicht von Transaktionen im Ethereum- Netzwerk.
  2. Berechtigte Transaktionen - Transaktionen sind im Wesentlichen privat, aber für mehrere Netzwerkteilnehmer, dh im öffentlichen Netzwerk, haben wir Informationen über die Transaktion und den Status ihrer Ausführung, aber anstelle der tatsächlichen Nutzlast im öffentlichen Netzwerk haben wir nur eine 64-Bit-Hash-Zeichenfolge Durch eine Kennung für eine reale Nutzlast in einem Enklavenknoten ist der Enklavenknoten selbst für das Signieren, Verschlüsseln, Speichern und Verteilen der Nutzlast zwischen den angegebenen Parteien der Transaktion verantwortlich.
  3. Private Transaktionen - unterscheidet sich von Berechtigten dadurch, dass die Transaktion nur für den Knoten verfügbar ist, der diese Transaktion erstellt hat. Andere Netzwerkteilnehmer können keine Nutzlasttransaktionen sehen .
    Ich werde diese Klassifizierung im gesamten Artikel verwenden.

gradle.build zeige ich Ihnen, wie die Build-Datei aussehen wird - gradle.build :


 plugins { id 'org.springframework.boot' version '2.1.6.RELEASE' id 'java' } apply plugin: 'io.spring.dependency-management' group = 'com.github' version = '1.0' sourceCompatibility = '1.8' configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } test { testLogging.showStandardStreams = true } dependencies { implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation group: 'org.web3j', name: 'quorum', version: '4.0.6' implementation group: 'org.web3j', name: 'core', version: '4.1.0' implementation group: 'org.web3j', name: 'codegen', version: '4.1.0' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'io.projectreactor:reactor-test' } task generateWrappers(type: JavaExec) { group 'Demo' description 'Generates wrappers for smart-contracts' classpath = sourceSets.main.runtimeClasspath main = 'com.github.quorum.utils.WrappersGenerator' } 

Ein bisschen Erklärung:


  1. org.web3j.core - Abhängigkeit für die Arbeit mit Transaktionen im Ethereum- Netzwerk und öffentlichen Transaktionen im Quorum- Netzwerk
  2. org.web3j.quorum - Abhängigkeit für die Arbeit mit privaten Transaktionen im Quorum- Netzwerk
  3. org.web3j.codegen - Abhängigkeit zum Generieren von org.web3j.codegen für Smart-Verträge von Solidity
  4. generateWrappers - Gradle-Task zum Generieren von Java- Wrappern aus soliden Solidity- Verträgen

Als nächstes zeige ich Ihnen den intelligenten Vertragscode, der in diesem Artikel verwendet wird: QuorumDemo.sol Datei:


 pragma solidity 0.5.0; /** * @dev Smart-Contract for demonstration purposes. */ contract QuorumDemo { string public user; /** * @dev Rewrite user name in storage. */ function writeUser(string calldata _user) public { user = _user; } } 

Der Vertrag wird absichtlich vereinfacht, reicht aber für die Zwecke unseres Artikels aus. Wenn Sie Solidity kennen, können Sie die Erklärung überspringen:


  • string public user - eine öffentliche Variable vom Typ string und der Name user . Im Gegensatz zu Java generiert Solidity automatisch einen Getter für öffentliche Variablen, sodass Sie ihn nicht manuell implementieren müssen.
  • function writeUser(...) - die Funktion zum Ändern des Werts einer Variablen, tatsächlich - Setter .

Um einen Java-wrapper aus einem intelligenten Vertrag zu erstellen, müssen Sie die Datei mit einem beliebigen Namen in den Ordner src/main/solidity/contracts QuorumDemo.sol , z. B. QuorumDemo.sol .
Führen Sie als Nächstes Gradle-task generateWrappers mit dem folgenden Befehl aus:


 gradle generateWrappers 

Nach Abschluss dieser Aufgabe wird unter src/main/java/com/github/quorum/component/wrappers ein Java-Wrapper erstellt, mit dem Sie bereits in Java- Code arbeiten können.


Damit das Backend Transaktionen signieren kann, müssen wir Nutzlasttransaktionen empfangen können, bevor wir sie senden. Zu diesem Zweck wäre es schön, es direkt von der Java-Wrapper- Klasse zu erhalten. Hier habe ich 2 Methoden im Wrapper erstellt. Die erste Methode gibt einfach den ABI des Vertrags zurück, mit dem ein neuer Smart-Vertrag heruntergeladen werden kann. Die zweite Methode ist die Bildung einer Transaktion, um den Status eines intelligenten Vertrags zu ändern. Hier ist der Code für diese Methoden:


 public static String getBinary() { return BINARY; } public static String getDataOnWriteUser(final String user) { final Function function = new Function( FUNC_WRITEUSER, Arrays.asList(new Utf8String(user)), Collections.emptyList() ); return FunctionEncoder.encode(function); } 

Durch Einfügen in den generierten Java-Wrapper können Sie Nutzdaten für Transaktionen empfangen.


Als nächstes benötigen wir eine bequeme Möglichkeit, Transaktionen an die Blockchain zu senden, vorzugsweise mit derselben Schnittstelle für private und öffentliche Transaktionen. Aus diesem Grund habe ich eine Transaktionsmanager-Schnittstelle und zwei ihrer Implementierungen erstellt:


  1. TesseraTransactionManager zum Senden privater Transaktionen
  2. GethTransactionManager zum Senden öffentlicher Transaktionen

Nehmen wir sie auseinander. TesseraTransactionManager Code:


 @Slf4j public class TesseraTransactionManager implements TransactionManager { private static final byte ATTEMPTS = 20; private static final int SLEEP_DURATION = 100; private final Quorum quorum; private final String fromAddress; private final QuorumTransactionManager quorumTxManager; private final TransactionReceiptProcessor txReceiptProcessor; public TesseraTransactionManager( Quorum quorum, Credentials credentials, String publicKey, List<String> privateFor, Tessera tessera ) { this.quorum = quorum; this.fromAddress = credentials.getAddress(); this.quorumTxManager = new QuorumTransactionManager(quorum, credentials, publicKey, privateFor, tessera); this.txReceiptProcessor = new PollingTransactionReceiptProcessor(quorum, SLEEP_DURATION, ATTEMPTS); } @Override public TransactionReceipt executeTransaction( final BigInteger gasPrice, final BigInteger gasLimit, final String to, final String data) { while (true) { try { final EthSendTransaction ethSendTx = sendTransaction(gasPrice, gasLimit, to, data); if (ethSendTx.hasError() && NONCE_TOO_LOW_ERROR_MESSAGE.equals(ethSendTx.getError().getMessage())) { log.warn("[BLOCKCHAIN] try to re-send transaction cause error {}", ethSendTx.getError().getMessage()); continue; } return processResponse(ethSendTx); } catch (TransactionException ex) { log.error("[BLOCKCHAIN] exception while receiving TransactionReceipt from Quorum node", ex); throw new RuntimeException(ex); } catch (Exception ex) { log.error("[BLOCKCHAIN] exception while sending transaction to Quorum node", ex); throw new RuntimeException(ex); } } } private EthSendTransaction sendTransaction( final BigInteger gasPrice, final BigInteger gasLimit, final String to, final String data) throws IOException { final BigInteger nonce = getNonce(); final RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, to, data); return this.quorumTxManager.signAndSend(rawTransaction); } private TransactionReceipt processResponse(final EthSendTransaction transactionResponse) throws IOException, TransactionException { if (transactionResponse.hasError()) { throw new RuntimeException( "[BLOCKCHAIN] error processing transaction request: " + transactionResponse.getError().getMessage() ); } final String transactionHash = transactionResponse.getTransactionHash(); return this.txReceiptProcessor.waitForTransactionReceipt(transactionHash); } private BigInteger getNonce() throws IOException { final EthGetTransactionCount ethGetTxCount = this.quorum.ethGetTransactionCount( this.fromAddress, DefaultBlockParameterName.PENDING).send(); return ethGetTxCount.getTransactionCount(); } } 

  • TransactionReceipt executeTransaction(...) - Implementierung der Schnittstelle, einer Methode zum Ausführen von Transaktionen im Netzwerk und zum Behandeln von Fehlern, falls diese auftreten. Gibt ein Objekt mit dem Ergebnis einer Transaktion zurück.
  • EthSendTransaction sendTransaction(...) - eine Methode zum Signieren und Senden von Transaktionen an die Blockchain. Gibt ein Objekt mit dem Transaktionsstatus und seinem Hash zurück.
  • TransactionReceipt processResponse(...) - eine Methode, die auf den Abschluss der Transaktion wartet und TransactionReceipt nach ihrer Ausführung zurückgibt;
  • BigInteger getNonce() - Gibt "nonce" aus dem Netzwerk zurück.

Und der GethTransactionManager Code:


 @Slf4j public class GethTransactionManager extends FastRawTransactionManager implements TransactionManager { private static final byte ATTEMPTS = 20; private static final int SLEEP_DURATION = 100; private final TransactionReceiptProcessor txReceiptProcessor; public GethTransactionManager(Web3j web3j, Credentials credentials) { this(web3j, credentials, new PollingTransactionReceiptProcessor(web3j, SLEEP_DURATION, ATTEMPTS)); } public GethTransactionManager(Web3j web3j, Credentials credentials, TransactionReceiptProcessor txReceiptProcessor) { super(web3j, credentials, txReceiptProcessor); this.txReceiptProcessor = txReceiptProcessor; } @Override public TransactionReceipt executeTransaction( final BigInteger gasPrice, final BigInteger gasLimit, final String to, final String data) { while (true) { try { final EthSendTransaction ethSendTx = sendTransaction(gasPrice, gasLimit, to, data, BigInteger.ZERO); if (ethSendTx != null && ethSendTx.hasError() && NONCE_TOO_LOW_ERROR_MESSAGE.equals(ethSendTx.getError().getMessage())) { log.warn("[BLOCKCHAIN] try to re-send transaction cause error: {}", ethSendTx.getError().getMessage()); continue; } return this.txReceiptProcessor.waitForTransactionReceipt(ethSendTx.getTransactionHash()); } catch (TransactionException ex) { log.error("[BLOCKCHAIN] exception while receiving TransactionReceipt from Quorum node", ex); throw new RuntimeException(ex); } catch (IOException ex) { log.error("[BLOCKCHAIN] exception while sending transaction to Quorum node", ex); throw new RuntimeException(ex); } } } @Override public EthSendTransaction sendTransaction( final BigInteger gasPrice, final BigInteger gasLimit, final String to, final String data, final BigInteger value ) throws IOException { return super.sendTransaction(gasPrice, gasLimit.add(BigInteger.valueOf(21_000L)), to, data, value); } } 

  • TransactionReceipt executeTransaction(...) - Implementierung der Schnittstelle, einer Methode zum Ausführen von Transaktionen im Netzwerk und zum Behandeln von Fehlern, falls diese auftreten. Gibt ein Objekt mit dem Ergebnis einer Transaktion zurück.
  • EthSendTransaction sendTransaction(...) ist eine Methode, die die EthSendTransaction sendTransaction(...) Methode aufruft, um eine Transaktion an die Blockchain zu senden.

Der Handler für Anforderungen, die an die API gesendet werden :


 @Slf4j @Component public class RequestHandler { private final Web3j web3j; private final Quorum quorum; private final Tessera tessera; private final Credentials credentials; private final BlockchainConfig blockchainConfig; private String deployedContract; @Autowired public RequestHandler( @Qualifier("initWeb3j") Web3j web3j, Quorum quorum, Tessera tessera, Credentials credentials, BlockchainConfig blockchainConfig ) { this.web3j = web3j; this.quorum = quorum; this.tessera = tessera; this.credentials = credentials; this.blockchainConfig = blockchainConfig; } /** * Deploy new smart-contract. * * @param serverRequest * - {@link ServerRequest} object with request information * @return {@link ServerResponse} object with response data */ public Mono<ServerResponse> deployContract(final ServerRequest serverRequest) { return serverRequest .bodyToMono(APIRequest.class) .map(this::getTransactionManager) .map(this::deployContract) .flatMap(this::generateResponse); } private TransactionManager getTransactionManager(final APIRequest apiRequest) { log.info("[HANDLER] privateFor = {}", apiRequest.getPrivateFor()); TransactionManager txManager; if (isPrivate(apiRequest.getPrivateFor())) { if (apiRequest.getPrivateFor().size() == 0) { apiRequest.getPrivateFor().add(this.blockchainConfig.getTesseraPublicKey()); } txManager = new TesseraTransactionManager(this.quorum, this.credentials, this.blockchainConfig.getTesseraPublicKey(), apiRequest.getPrivateFor(), this.tessera); } else { txManager = new GethTransactionManager(this.web3j, this.credentials); } return txManager; } private boolean isPrivate(final List<String> limitedTo) { return limitedTo == null || limitedTo.size() == 0 || !limitedTo.get(0).equals("public"); } private APIResponse deployContract(final TransactionManager txManager) { log.info("[HANDLER] deploying new smart-contract"); final String data = QuorumDemo.getBinary(); final TransactionReceipt txReceipt = txManager.executeTransaction(GAS_PRICE, DEPLOY_GAS_LIMIT, null, data); final APIResponse apiResponse = APIResponse.newInstance(txReceipt); this.deployedContract = txReceipt.getContractAddress(); log.info("[HANDLER] contract has been successfully deployed. Result: {}", apiResponse.getMap()); return apiResponse; } private Mono<ServerResponse> generateResponse(final APIResponse apiResponse) { return ServerResponse .ok() .body(Mono.just(apiResponse.getMap()), Map.class); } /** * Send transaction on update user in smart-contract. * * @param serverRequest * - {@link ServerRequest} object with request information * @return {@link ServerResponse} object with response data */ public Mono<ServerResponse> updateUser(final ServerRequest serverRequest) { return serverRequest .bodyToMono(APIRequest.class) .map(this::sendTransaction) .flatMap(this::generateResponse); } private APIResponse sendTransaction(final APIRequest apiRequest) { final TransactionManager txManager = getTransactionManager(apiRequest); log.info("[HANDLER] sending new transaction"); final String data = QuorumDemo.getDataOnWriteUser(apiRequest.getUser()); final TransactionReceipt txReceipt = txManager.executeTransaction(GAS_PRICE, TX_GAS_LIMIT, this.deployedContract, data); final APIResponse apiResponse = APIResponse.newInstance(txReceipt); log.info("[HANDLER] transaction has been successfully executed. Result: {}", apiResponse.getMap()); return apiResponse; } /** * Read user from smart-contract. * * @param serverRequest * - {@link ServerRequest} object with request information * @return {@link ServerResponse} object with response data */ public Mono<ServerResponse> getUser(final ServerRequest serverRequest) { final APIResponse apiResponse = getUser(); return generateResponse(apiResponse); } private APIResponse getUser() { log.info("[HANDLER] reading user from smart-contract"); final QuorumDemo quorumDemo = QuorumDemo.load(this.deployedContract, this.web3j, this.credentials, new StaticGasProvider(GAS_PRICE, DEPLOY_GAS_LIMIT)); final String user = readUserFromSmartContract(quorumDemo); final APIResponse apiResponse = APIResponse.newInstance(user); log.info("[HANDLER] user: '{}'", user); return apiResponse; } private String readUserFromSmartContract(final QuorumDemo quorumDemo) { try { return quorumDemo.user().send().getValue(); } catch (Exception ex) { log.info("[HANDLER] exception while reading user from smart-contract: {}", ex); return null; } } } 

Jetzt werde ich erklären, welche Methoden für was verantwortlich sind.
Mono<ServerResponse> deployContract(...) - beschreibt die allgemeine Logik der Mono<ServerResponse> deployContract(...) öffentlichen und privaten Smart Contract.
TransactionManager getTransactionManager(...) Methode TransactionManager getTransactionManager(...) - TransactionManager getTransactionManager(...) das Implementierungsobjekt des Transaktionsmanagers abhängig von der Art der Transaktion zurück. Zu diesem Zweck enthält der Anforderungsparameter den Parameter privateFor , bei dem es sich um ein Array von Zeichenfolgen öffentlicher Tessera- Schlüssel handelt.
boolean isPrivate(...) Methode boolean isPrivate(...) - gibt "true" zurück, wenn der Parameter privateFor entweder leer ist ( private Transaktion) oder eine Liste öffentlicher Schlüssel enthält ( autorisierte Transaktion). Gibt "false" zurück, wenn der Parameter privateFor nicht leer ist und das erste Array-Element gleich "public" ist.
APIResponse deployContract(...) -Methode - sendet die Bereitstellungstransaktion an die Blockchain.
Mono<ServerResponse> generateResponse(...) - generiert ein Objekt mit einer Antwort an den Client.
Mono<ServerResponse> updateUser(...) - beschreibt die allgemeine Logik der Transaktion zum Ändern des Status des Smart-Vertrags.
APIResponse sendTransaction(...) -Methode - sendet eine APIResponse sendTransaction(...) an die Blockchain.
APIResponse getUser() -Methode - beschreibt die allgemeine Logik zum Lesen von Informationen aus einem intelligenten Vertrag und gibt eine Antwort an den Client zurück.
String readUserFromSmartContract(...) Methode String readUserFromSmartContract(...) - liest den Status aus dem Smart-Vertrag und gibt das Ergebnis zurück.


Der vollständige Anwendungscode ist im GitHub- Repository verfügbar. Ein Link dazu befindet sich am Ende dieses Artikels.


Überprüfen Sie


Um alle drei Arten von Transaktionen zu testen, habe ich Testklassen geschrieben (der Code befindet sich im GitHub- Repository). Zu diesem Zweck habe ich eine Blockchain mit 3 Quorum- Knoten (3 Geth- Knoten + 3 Tessera- Knoten) bereitgestellt. 3 Quorum- Knoten sind die Mindestknoten, die zum Überprüfen aller Arten von Transaktionen erforderlich sind. Denken Sie daran, wenn Sie es selbst ausprobieren möchten.


Öffentliche Transaktionen


Um einen Testfall mit einer öffentlichen Transaktion auszuführen, müssen Sie den folgenden Befehl ausführen:


 gradle test --tests *.PublicTransactionsTests 

Dieser Testfall sendet 3 API- Anforderungen. Das erste ist die Bereitstellung des Smart-Vertrags in der Blockchain, das zweite ist die Änderung des Vertragsstatus und die dritte Anforderung ist das Lesen von Informationen aus dem Smart-Vertrag. Als Ergebnis des Tests werden ungefähr die folgenden Protokolle angezeigt (Adressen in Ihrem Netzwerk sowie Transaktions-Hashes unterscheiden sich):


 [HANDLER] privateFor = [public] [HANDLER] deploying new smart-contract [HANDLER] contract has been successfully deployed. Result: {contract_address=0xf9425b94e459805da09950f5988071692d925097, transaction_hash=0x31bc179f8cd12c640d1663f3df51ce6da1fbc2875f2b724c3911108fcd19a5d0} [HANDLER] privateFor = [public] [HANDLER] sending new transaction [HANDLER] transaction has been successfully executed. Result: {contract_address=null, transaction_hash=0x33ba66d5deec33f3142bfa190a0d37d0ff07c2e66b06037f5b5ff9578154a3ff} [HANDLER] reading user from smart-contract [HANDLER] user: 'Public Test User' 

Im Allgemeinen zeigen diese Protokolle an, dass alle drei Vorgänge erfolgreich waren. Die ersten 3 Protokolle - gehören zur Anforderung der Bereitstellung des Smart-Vertrags, die nächsten 3 Protokolle - gehören zur Transaktion und die letzten 2 - zum Lesen der Informationen aus dem Smart-Vertrag.
Die Tatsache, dass beim Laden des Vertrags die Vertragsadresse angezeigt wird, bei einer einfachen Transaktion jedoch - nein, dies ist ganz normal, da wir den Vertrag zum zweiten Mal nicht bereitstellen, sondern die Transaktion für einen vorhandenen Smart-Vertrag ausführen.


Lassen Sie uns nun überprüfen, was Geth uns zeigt , und den folgenden Befehl ausführen, um eine Verbindung zur IPC- Schnittstelle des Geth- Prozesses des Clients herzustellen :


 geth attach /path/to/ipc 

Nachdem wir uns an den Prozess gewöhnt haben, können Sie alle erforderlichen Informationen vollständig überprüfen. Schauen wir uns eine TransactionReceipt Transaktion zur Bereitstellung eines neuen Smart-Vertrags an, indem Sie den Befehl ausführen (der Transaktions-Hash muss eingerichtet und aus den Testprotokollen entnommen werden):


 web3.eth.getTransactionReceipt('0x31bc179f8cd12c640d1663f3df51ce6da1fbc2875f2b724c3911108fcd19a5d0'); 

Als Ergebnis sehen wir Folgendes:



Wir interessieren uns für folgende Parameter:


  • "contractAddress" - wenn nicht "null", dann verstehen wir, dass dies eine Transaktion für die Bereitstellung eines intelligenten Vertrags ist;
  • "status" - in diesem Fall ist es gleich "0x1" - was bedeutet, dass die Transaktion erfolgreich war.

Schauen wir uns die Transaktion selbst an. Durch Ausführen des Befehls:


 web3.eth.getTransaction('0x31bc179f8cd12c640d1663f3df51ce6da1fbc2875f2b724c3911108fcd19a5d0'); 

Ergebnis:



Hier interessieren uns folgende Parameter:


  • "Eingabe" ist eine Nutzlasttransaktion ;
  • "v" - im Allgemeinen ist dies ein Parameter für ECDSA , den Algorithmus für digitale Signaturen, aber jetzt interessieren wir uns für etwas anderes - die Bedeutung der Variablen. Dies ist wichtig, da es bei öffentlichen und privaten Transaktionen unterschiedlich sein wird. "0x1c" ("28" im Dezimalsystem) und "0x1b" ("27" im Dezimalsystem) sind typisch für öffentliche Transaktionen und "0x25" ("37" im Dezimalsystem) und "0x26" ("38" im Dezimalsystem) System) - Dies sind private Transaktionscodes.

Sie können auch überprüfen, ob sich die Informationen auf anderen Knoten nicht von denen unterscheiden, die wir jetzt gesehen haben.


Jetzt können Sie die Transaktionsstatusänderungen des Smart-Vertrags anzeigen. Führen Sie den folgenden Befehl aus:


 web3.eth.getTransactionReceipt('0x33ba66d5deec33f3142bfa190a0d37d0ff07c2e66b06037f5b5ff9578154a3ff'); 

Ergebnis:



Wir interessieren uns für folgende Parameter:


  • "to" - wir sehen, dass die Transaktion an den gerade erwarteten Smart-Vertrag ging;
  • "status" - entspricht "0x1", was bedeutet, dass die Transaktion erfolgreich war.

Transaktion:



Nichts Ungewöhnliches, aber Sie können die Informationen auf anderen Knoten überprüfen. Dies ist nützlich.


Private Transaktionen


Um einen Testfall mit einer privaten Transaktion auszuführen, müssen Sie den folgenden Befehl ausführen:


 gradle test --tests *.PrivateTransactionsTests 

Wie im Testfall bei öffentlichen Transaktionen wird in diesem Testfall ein neuer Smart-Vertrag bereitgestellt, eine Statusänderungstransaktion ausgeführt und Informationen aus der Änderung im Smart-Vertrag gelesen.


Infolgedessen schreibt das Programm die folgenden Protokolle:


 [HANDLER] privateFor = [] [HANDLER] deploying new smart-contract [HANDLER] contract has been successfully deployed. Result: {contract_address=0x3e2284d92842f781b83cc7e56fbb074ab15f9a90, transaction_hash=0x8fd619bd9a526f83e29d7b417551e174862f7503ef430eb45793509d05039595} [HANDLER] privateFor = [] [HANDLER] sending new transaction [HANDLER] transaction has been successfully executed. Result: {contract_address=null, transaction_hash=0x72a0458a7b313c8a1c18269ae160e140c6a6e41cb2fd087c64cf665b08a6aefb} [HANDLER] reading user from smart-contract [HANDLER] user: 'Private Test User' 

Die Änderung im Vergleich zu öffentlichen Transaktionen ist der Parameter privateFor - jetzt hat sie den Wert eines leeren Arrays.
Lassen Sie uns TransactionReceipt auf eine Transaktion überprüfen. Team:


 web3.eth.getTransactionReceipt('0x8fd619bd9a526f83e29d7b417551e174862f7503ef430eb45793509d05039595'); 

Ergebnis:



Von den Änderungen im Vergleich zu öffentlichen Transaktionen ist anzumerken, dass Sie nicht sehen werden, wie viel Gas für die Transaktion ausgegeben wurde - gasUsed und cumulativeGasUsed haben den Wert "0".
Schauen wir uns nun die Transaktion selbst an. Führen Sie den folgenden Befehl aus:


 web3.eth.getTransaction('0x8fd619bd9a526f83e29d7b417551e174862f7503ef430eb45793509d05039595'); 

Als Ergebnis werden wir Folgendes sehen:



Was ist bei dieser Transaktion zu beachten:


  1. Wie bereits am Anfang dieses Artikels erwähnt, wird im Eingabefeld anstelle einer echten Nutzlasttransaktion eine feste Zeile mit 64 Byte (128 Zeichen) angezeigt . Diese Zeile ist die Kennung für die Daten im Tessera- Repository. Auf Anfrage können Sie echte Daten an Tessera senden .
  2. "v" - Anstelle der Codes "0x1c" oder "0x1b" wie bei öffentlichen Transaktionen wird bei privaten Transaktionen "0x26" oder "0x25" angezeigt.

Überprüfen wir nun den TransactionReceipt und die Transaktion selbst, um den Status des Vertrags zu ändern (Sie kennen die Befehle bereits). Ergebnis:




Grundsätzlich werden wir aus dieser privaten Transaktion nichts Neues lernen.


Berechtigte Transaktionen


Da es sich auch um private Transaktionen handelt, sind sie einfach privat, nicht für einen Knoten, sondern für mehrere. Die Ergebnisse solcher Transaktionen unterscheiden sich nicht von privaten Transaktionen. Sie können einen Unterschied machen, wenn Sie versuchen, Informationen von einem in privateFor angegebenen Knoten und von einem Knoten abzurufen, dessen öffentlicher Schlüssel nicht in privateFor registriert ist (Sie können Informationen vom ersten Knoten abrufen und nicht vom zweiten).
Um einen Testfall mit privaten Transaktionen für mehrere Netzwerkteilnehmer (berechtigte Transaktionen) auszuführen, müssen Sie den folgenden Befehl ausführen:


 gradle test --tests *.PermissionTransactionsTests 

Java API- Protokolle:


 [HANDLER] privateFor = [wQTHrl/eqa7TvOz9XJcazsp4ZuncfxHb8c1J1njIOGA=] [HANDLER] deploying new smart-contract [HANDLER] contract has been successfully deployed. Result: {contract_address=0xf1cc0ba22bd0d18fc9acb22dd57795a3f2fb4ebd, transaction_hash=0x585980bec88aa8a0fe5caffe6d6f24b82d3cd381fcf72fdd8e2102ce67799f01} [HANDLER] privateFor = [wQTHrl/eqa7TvOz9XJcazsp4ZuncfxHb8c1J1njIOGA=] [HANDLER] sending new transaction [HANDLER] transaction has been successfully executed. Result: {contract_address=null, transaction_hash=0x47edc0d00fa9447b2da9f5a78f44602f96145497238cb1ce1d879afb351a3cbe} [HANDLER] reading user from smart-contract [HANDLER] user: 'Permissioned Test User' 

Die Ergebnisse im Geth- Client zur Bereitstellung des neuen Smart-Vertrags TransactionReceipt bzw. der Transaktion selbst:




Und die Statusänderungstransaktion, TransactionReceipt und die Transaktion selbst:




HTTP-Anfragen


Trotz der Tatsache, dass wir gesehen haben, wie sich öffentliche Transaktionen aus Sicht des Geth- Kunden von privaten unterscheiden, zeigt dies keine wirkliche Einschränkung bei der Informationsbeschaffung. Um Ihnen zu zeigen, dass es wirklich möglich ist, die Anzahl der Knoten zu begrenzen, die Ihre Transaktion lesen können, werde ich mit CURL mehrere Anforderungen für 3 Knoten stellen, um Informationen aus dem Smart-Vertrag zu lesen (die Anforderungen betreffen private und unterbrochene Transaktionen).
HTTP-Anforderungen haben zwei Parameter im Anforderungshauptteil:


  1. "Endpunkt" - Direkt Endpunkt zum Quorum- Knoten, Sie müssen eine Verbindung zum Knoten herstellen.
  2. "contractAddress" ist die Adresse des Vertrags, aus dem die Daten gelesen werden.

In meinem Fall hat "endopint" einen Host - localhost - aber verschiedene Ports für 3 Quorum- Knoten: 22000 (alle Transaktionen wurden von diesem Knoten aus ausgeführt), 22001 (sein öffentlicher Schlüssel wurde in autorisierten Transaktionen angegeben), 22002 (sollte keinen Zugriff haben Informationen).


Beginnen wir mit einer privaten Transaktion (nur ein Knoten am 22000-Port sollte Informationen in einem Smart-Vertrag anzeigen können).


CURL- Anforderung auf dem Knoten, der die Transaktion durchgeführt hat:


 curl -X POST \ http://127.0.0.1:8080/user \ -H 'Content-Type: application/json' \ -d '{ "endpoint": "http://127.0.0.1:22000", "contractAddress": "0x3e2284d92842f781b83cc7e56fbb074ab15f9a90" }' 

Als Ergebnis haben wir Folgendes erhalten:


 {"data":{"user":"Private Test User"}} 

Dies bedeutet, dass der Knoten Informationen in einem intelligenten Vertrag anzeigen kann.


Nun wollen wir sehen, was der Knoten am 22001-Port zu uns zurückgibt. CURL- Anfrage:


 curl -X POST \ http://127.0.0.1:8080/user \ -H 'Content-Type: application/json' \ -d '{ "endpoint": "http://127.0.0.1:22001", "contractAddress": "0x3e2284d92842f781b83cc7e56fbb074ab15f9a90" }' 

Großartig! :


 {"data":{"status_code":500,"description":"Something went wrong"}} 

, - — !


, 3- . CURL :


 curl -X POST \ http://127.0.0.1:8080/user \ -H 'Content-Type: application/json' \ -d '{ "endpoint": "http://127.0.0.1:22002", "contractAddress": "0x3e2284d92842f781b83cc7e56fbb074ab15f9a90" }' 

Großartig! API :


 {"data":{"status_code":500,"description":"Something went wrong"}} 

, . "permissioned" .


CURL "permissioned" - , 22000:


 curl -X POST \ http://127.0.0.1:8080/user \ -H 'Content-Type: application/json' \ -d '{ "endpoint": "http://127.0.0.1:22000", "contractAddress": "0xf1cc0ba22bd0d18fc9acb22dd57795a3f2fb4ebd" }' 

:


 {"data":{"user":"Permissioned Test User"}} 

, , , .


- , -, . CURL :


 curl -X POST \ http://127.0.0.1:8080/user \ -H 'Content-Type: application/json' \ -d '{ "endpoint": "http://127.0.0.1:22001", "contractAddress": "0xf1cc0ba22bd0d18fc9acb22dd57795a3f2fb4ebd" }' 

Großartig! :


 {"data":{"user":"Permissioned Test User"}} 

, , . CURL :


 curl -X POST \ http://127.0.0.1:8080/user \ -H 'Content-Type: application/json' \ -d '{ "endpoint": "http://127.0.0.1:22002", "contractAddress": "0xf1cc0ba22bd0d18fc9acb22dd57795a3f2fb4ebd" }' 

Großartig! -. .


Fazit


, Quorum blockchain Java . , - .


:


  1. Quorum
  2. Quorum
  3. GitHub
  4. Quorum Slack

Vielen Dank für Ihre Aufmerksamkeit!

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


All Articles