Bonjour, Habr!
Pour ceux qui sont intéressés par le sujet de la blockchain, ce n'est un secret pour personne qu'en plus des blockchains publiques, comme Ethereum , Bitcoin , Zcash , etc., il y a aussi leurs "frères" d'entreprise (privés) "qui sont en quelque sorte meilleurs que les réseaux publics, mais dans quelque chose qu'ils perdent pour eux. Parmi les réseaux les plus connus, je pense que vous pouvez nommer Quorum (fournisseur - JP Morgan Chase ), Pantheon (fournisseur - PegaSys ) et Hyperledger (géré par The Linux Foundation ). Malgré le fait qu'il y a beaucoup de décisions publiques, les entreprises sont de plus en plus intéressées par les blockchains privées car elles sont en mesure de fournir le niveau de confidentialité nécessaire, les transactions sont plus rapides et ainsi de suite. Les différences entre les chaînes de blocs privées et publiques, ainsi que leurs avantages et inconvénients, ne font pas l'objet de cet article. Si vous êtes intéressé à lire à ce sujet, c'est par exemple un tel article sur Medium .
Dans cet article, je voudrais vous expliquer comment vous pouvez utiliser la blockchain Quorum pour développer vos applications avec la prise en charge des transactions privées et publiques. Pour démontrer les capacités, nous allons écrire une petite application Java / Spring qui acceptera les demandes de déployer (déployer) des contrats intelligents, d'exécuter des transactions et de lire les données d'un contrat intelligent. En fait, voici la pile technologique qui sera utilisée dans l'article:
Quorum est un projet avec du code open source sur GitHub , dont le but est de fournir une blockchain qui permettrait d'effectuer des transactions non seulement publiquement mais également en mode privé. D'un point de vue technique, Quorum est une mise à niveau d' Ethereum , il a également son propre client Geth modifié pour pouvoir effectuer des transactions privées.
Un ajout important est également les services enclaves , qui sont responsables du stockage, du cryptage et de la distribution des transactions privées entre eux. Il existe maintenant 2 services d' enclave de ce type :
- Constellation - écrite en Haskell, la première version de l' enclave , mais maintenant elle ne se développe plus, et très probablement à l'avenir, elle sera abandonnée au profit d'une nouvelle;
- Tessera - un nouveau service, écrit en Java , soutenu par les développeurs de JP Morgan Chase, a plus d'options pour l'intégration avec la base de données et la gestion des informations sensibles (par exemple, il y a une option d'intégration avec HashiCorp Vault pour la gestion des secrets).
Quant aux transactions, du point de vue de l'interface d' Ethereum ordinaire , peu de choses ont changé (et c'est bien). Pour envoyer une transaction privée, en plus des informations habituelles sur la transaction, vous devez également spécifier le paramètre privateFor - il s'agit du tableau de lignes, et ces lignes sont des clés publiques du nœud enclave . À l'aide de ces clés, les transactions de charge utile sont cryptées et la charge utile est répartie entre les nœuds Tessera à l'intérieur de la blockchain.
Pour une connaissance plus approfondie de Quorum , comment cela fonctionne et comment développer le réseau de blockchain, vous pouvez le trouver sur le site officiel (un lien vers la documentation, ainsi qu'un lien vers le tutoriel sur la façon de lancer une blockchain de test, je vais laisser à la fin de l'article).
Développement d'applications Java
À titre d'exemple, je vais montrer une petite API RESTful écrite en Java / Spring , avec Gradle comme outil de gestion de construction et de dépendance qui chargera le contrat intelligent dans la blockchain, remplira la fonction de changer l'état du contrat et lira l'état du contrat intelligent.
Avant de commencer le développement lui-même, je dois clarifier quelque chose. Malgré le fait que Quorum dispose officiellement de 2 options de transaction, je préfère les diviser en 3 types:
- Transactions publiques - les transactions sont entièrement visibles pour tous les participants au réseau (y compris la charge utile ), le nœud enclave ne participe ni au traitement ni au stockage de la transaction. Les transactions publiques dans Quorum ne sont pas différentes des transactions sur le réseau Ethereum ;
- Transactions autorisées - les transactions sont essentiellement privées, mais pour plusieurs participants au réseau, c'est-à -dire sur le réseau public, nous avons des informations sur la transaction et l'état de son exécution, mais au lieu d'une charge utile réelle sur le réseau public, nous n'avons qu'une chaîne de hachage de 64 bits, qui est un identifiant pour une charge utile réelle dans un nœud d' enclave , le nœud d' enclave lui-même est responsable de la signature, du cryptage, du stockage et de la distribution de la charge utile entre les participants à la transaction spécifiés;
- Transactions privées - diffère des autorisations dans la mesure où la transaction n'est disponible que pour le nœud qui a créé cette transaction, les autres participants au réseau ne peuvent pas voir les transactions de charge utile .
J'utiliserai cette classification tout au long de l'article.
Pour commencer, je vais vous montrer Ă quoi ressemblera le fichier de construction - 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' }
Un peu d'explication:
org.web3j.core
- dépendance pour travailler avec les transactions sur le réseau Ethereum et les transactions publiques sur le réseau Quorumorg.web3j.quorum
- dépendance pour travailler avec des transactions privées sur le réseau Quorumorg.web3j.codegen
- dépendance pour générer des org.web3j.codegen
pour les contrats intelligents Solidity- generateWrappers - Gradle-task pour générer des wrappers Java à partir de contrats intelligents Solidity
Ensuite, je vais vous montrer le code de contrat intelligent qui sera utilisé dans cet article: Fichier QuorumDemo.sol
:
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; } }
Le contrat est intentionnellement simplifié, mais il suffit aux fins de notre article. Si vous connaissez Solidity , vous pouvez ignorer l'explication:
string public user
- une variable publique de type chaîne et le nom d' utilisateur . Contrairement à Java , Solidity génère automatiquement un getter pour les variables publiques, vous n'avez donc pas besoin de l'implémenter manuellement.function writeUser(...)
- la fonction de changer la valeur d'une variable, en fait - setter .
Pour créer un Java-wrapper
partir d'un contrat intelligent, vous devez placer le fichier dans le dossier src/main/solidity/contracts
avec n'importe quel nom, par exemple QuorumDemo.sol
.
Ensuite, exécutez Gradle-task generateWrappers avec la commande:
gradle generateWrappers
Une fois cette tâche terminée, un wrapper Java sera créé dans src/main/java/com/github/quorum/component/wrappers
, avec lequel vous pouvez déjà travailler en code Java .
Pour que le backend puisse signer des transactions, nous devons pouvoir recevoir des transactions de charge utile avant de les envoyer. Pour cela, il serait intéressant de l'obtenir directement à partir de la classe Java-wrapper . Ici, j'ai créé 2 méthodes dans le wrapper. La première méthode renvoie simplement l' ABI du contrat, qui peut être utilisé pour télécharger un nouveau contrat intelligent. La deuxième méthode est la formation d'une transaction pour changer le statut d'un contrat intelligent. Voici le code de ces méthodes:
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); }
En les insérant dans le wrapper Java généré, vous pouvez recevoir la charge utile des transactions.
Ensuite, nous avons besoin d'un moyen pratique d'envoyer des transactions à la blockchain, de préférence avec la même interface pour les transactions privées et publiques. Par conséquent, j'ai créé une interface de gestionnaire de transactions et 2 de son implémentation:
TesseraTransactionManager
, pour l'envoi de transactions privéesGethTransactionManager
, pour l'envoi de transactions publiques
Prenons-les Ă part. Code TesseraTransactionManager
:
@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(...)
- implémentation de l'interface, une méthode pour effectuer des transactions sur le réseau et gérer les erreurs si elles se produisent. Renvoie un objet avec le résultat d'une transaction;EthSendTransaction sendTransaction(...)
- une méthode pour signer et envoyer des transactions à la blockchain. Renvoie un objet avec le statut de transaction et son hachage;TransactionReceipt processResponse(...)
- une méthode qui attend que la transaction se termine et renvoie TransactionReceipt
après son exécution;BigInteger getNonce()
- Retourne "nonce" du réseau.
Et le code GethTransactionManager
:
@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(...)
- implémentation de l'interface, une méthode pour effectuer des transactions sur le réseau et gérer les erreurs si elles se produisent. Renvoie un objet avec le résultat d'une transaction;EthSendTransaction sendTransaction(...)
est une méthode qui appelle la méthode EthSendTransaction sendTransaction(...)
pour envoyer une transaction Ă la blockchain.
Le gestionnaire des demandes qui parviennent Ă l' API :
@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; } } }
Je vais maintenant expliquer quelles méthodes sont responsables de quoi.
Mono<ServerResponse> deployContract(...)
méthode Mono<ServerResponse> deployContract(...)
- décrit la logique générale du Mono<ServerResponse> deployContract(...)
contrat intelligent, public et privé.
TransactionManager getTransactionManager(...)
méthode TransactionManager getTransactionManager(...)
- renvoie l'objet d'implémentation du gestionnaire de transactions en fonction du type de transaction. Pour cela, le paramètre request contiendra le paramètre privateFor , qui est un tableau de chaînes de clés publiques Tessera .
boolean isPrivate(...)
méthode boolean isPrivate(...)
- renvoie "true" si le paramètre privateFor est vide (transaction privée ) ou a une liste de clés publiques (transaction autorisée ). Renvoie "false" si le paramètre privateFor n'est pas vide et que le premier élément du tableau est égal à "public".
APIResponse deployContract(...)
- envoie la transaction de déploiement à la blockchain.
Méthode Mono<ServerResponse> generateResponse(...)
- génère un objet avec une réponse au client.
Méthode Mono<ServerResponse> updateUser(...)
- décrit la logique générale de la transaction pour changer le statut du contrat intelligent.
APIResponse sendTransaction(...)
- envoie une transaction de changement d'état à la blockchain.
APIResponse getUser()
- décrit la logique générale de lecture des informations d'un contrat intelligent et renvoie une réponse au client.
Méthode de String readUserFromSmartContract(...)
- lit l'état du contrat intelligent et renvoie le résultat.
Le code d'application complet est disponible dans le référentiel GitHub , un lien vers lequel sera à la fin de cet article.
Vérifier
Pour tester les 3 types de transactions, j'ai écrit des classes de test (le code est dans le dépôt GitHub ). Pour ce faire, j'ai déployé une blockchain avec 3 nœuds Quorum (3 nœuds Geth + 3 nœuds Tessera ). 3 Les nœuds de quorum sont les nœuds minimaux requis pour vérifier tous les types de transactions. Gardez cela à l'esprit si vous voulez l'essayer vous-même.
Transactions publiques
Pour exécuter un scénario de test avec une transaction publique, vous devez exécuter la commande suivante:
gradle test --tests *.PublicTransactionsTests
Ce scénario de test enverra 3 demandes d' API . Le premier est le déploiement du contrat intelligent sur la blockchain, le second est le changement de statut du contrat et la troisième demande est la lecture des informations du contrat intelligent. À la suite du test, vous verrez approximativement les journaux suivants (les adresses sur votre réseau différeront, ainsi que les hachages de transaction):
[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'
En général, ces journaux indiquent que les 3 opérations ont réussi. Les 3 premiers journaux - appartiennent à la demande de déploiement du contrat intelligent, les 3 prochains journaux - appartiennent à la transaction et les 2 derniers - pour lire les informations du contrat intelligent.
Le fait que dans le résultat du chargement du contrat, nous voyons contract_address , mais dans le cas d'une transaction simple - non, c'est tout à fait normal, car la deuxième fois, nous ne déployons pas le contrat, mais réalisons la transaction sur un contrat intelligent existant.
Vérifions maintenant ce que Geth nous montre, exécutons la commande suivante pour nous connecter à l'interface IPC du processus Geth du client:
geth attach /path/to/ipc
Une fois que nous nous sommes «habitués» au processus, vous pouvez revoir complètement toutes les informations nécessaires. Examinons la TransactionReceipt
TransactionReceipt sur le déploiement d'un nouveau contrat intelligent en exécutant la commande (le hachage de la transaction doit être configuré et extrait des journaux de test):
web3.eth.getTransactionReceipt('0x31bc179f8cd12c640d1663f3df51ce6da1fbc2875f2b724c3911108fcd19a5d0');
En conséquence, nous voyons ce qui suit:

Nous nous intéressons aux paramètres suivants:
- "contractAddress" - sinon "null", alors nous comprenons qu'il s'agit d'une transaction pour le déploiement d'un contrat intelligent;
- "status" - dans ce cas, il est égal à "0x1" - ce qui signifie que la transaction a réussi.
Et regardons la transaction elle-même. En exécutant la commande:
web3.eth.getTransaction('0x31bc179f8cd12c640d1663f3df51ce6da1fbc2875f2b724c3911108fcd19a5d0');
Résultat:

Nous nous intéressons ici aux paramètres suivants:
- "input" est une transaction de charge utile ;
- "v" - en général, c'est un paramètre pour ECDSA , l'algorithme de signature numérique, mais maintenant nous nous intéressons à autre chose - la signification de la variable. C'est important parce que dans les transactions publiques et privées, ce sera différent. "0x1c" ("28" dans le système décimal) et "0x1b" ("27" dans le système décimal) sont typiques pour les transactions publiques, et "0x25" ("37" dans le système décimal) et "0x26" ("38" en décimal). système) - ce sont des codes de transaction privés.
Vous pouvez également vérifier que sur d'autres nœuds, les informations ne diffèrent pas de ce que nous avons vu maintenant.
Vous pouvez maintenant afficher les modifications de l'état des transactions du contrat intelligent. Exécutez la commande:
web3.eth.getTransactionReceipt('0x33ba66d5deec33f3142bfa190a0d37d0ff07c2e66b06037f5b5ff9578154a3ff');
Résultat:

Nous nous intéressons aux paramètres suivants:
- "to" - nous voyons que la transaction est allée au contrat intelligent tant attendu;
- "status" - il est égal à "0x1", ce qui signifie que la transaction a réussi.
Transaction:

Rien d'inhabituel, mais vous pouvez vérifier les informations sur les autres nœuds, c'est utile.
Transactions privées
Pour exécuter un scénario de test avec une transaction privée, vous devez exécuter la commande suivante:
gradle test --tests *.PrivateTransactionsTests
Comme dans le cas de test avec les transactions publiques, ce cas de test déploiera un nouveau contrat intelligent, exécutera une transaction de changement d'état et lira les informations du changement dans le contrat intelligent.
Par conséquent, le programme écrira les journaux suivants:
[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'
Le changement, par rapport aux transactions publiques, est le paramètre privateFor - il a maintenant la valeur d'un tableau vide.
Vérifions TransactionReceipt
pour une transaction. Équipe:
web3.eth.getTransactionReceipt('0x8fd619bd9a526f83e29d7b417551e174862f7503ef430eb45793509d05039595');
Résultat:

Parmi les changements, par rapport aux transactions publiques, il convient de dire que vous ne verrez pas la quantité de gaz dépensée pour la transaction - gasUsed et cumulativeGasUsed ont une valeur de "0".
Voyons maintenant la transaction elle-même. Exécutez la commande:
web3.eth.getTransaction('0x8fd619bd9a526f83e29d7b417551e174862f7503ef430eb45793509d05039595');
En conséquence, nous verrons ceci:

Ce qui mérite d'être noté dans cette transaction:
- Comme je l'ai mentionné au début de cet article, au lieu d'une véritable transaction de charge utile , vous verrez une ligne fixe de 64 octets (128 caractères) dans le champ de saisie . Cette ligne est l'identifiant des données dans le référentiel Tessera , vous pouvez obtenir des données réelles sur demande auprès de Tessera .
- "v" - au lieu des codes "0x1c" ou "0x1b" comme dans les transactions publiques, pour les transactions privées, vous verrez "0x26" ou "0x25".
Vérifions maintenant le TransactionReceipt
et la transaction elle-même pour changer le statut du contrat (vous connaissez déjà les commandes). Résultat:


En principe, nous n'apprendrons rien de nouveau de cette transaction privée.
Transactions autorisées
Comme il s'agit également de transactions privées, elles sont simplement privées, non pas pour un nœud, mais pour plusieurs, les résultats de ces transactions ne différeront pas des transactions privées. Vous pouvez faire une différence si vous essayez d'obtenir des informations d'un nœud qui a été spécifié dans privateFor et d'un nœud dont la clé publique n'est pas enregistrée dans privateFor (vous pouvez obtenir des informations du premier nœud et pas du deuxième).
Pour exécuter un scénario de test avec des transactions privées pour plusieurs participants au réseau (transactions autorisées), vous devez exécuter la commande suivante:
gradle test --tests *.PermissionTransactionsTests
Journaux de l' API Java :
[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'
Les résultats dans le client Geth , sur le déploiement du nouveau contrat intelligent, TransactionReceipt
et la transaction elle-mĂŞme, respectivement:


Et la transaction de changement d'état, TransactionReceipt
et la transaction elle-mĂŞme:


Demandes HTTP
Malgré le fait que nous avons vu comment les transactions publiques diffèrent des transactions privées du point de vue du client Geth , cela ne montre pas une réelle restriction à l'obtention d'informations. Par conséquent, afin de vous montrer qu'il est vraiment possible de limiter le nombre de nœuds pouvant lire votre transaction, je vais effectuer plusieurs requêtes en utilisant CURL pour 3 nœuds pour lire les informations du smart contract (les requêtes concerneront des transactions privées et persécutées ).
Les requêtes HTTP auront 2 paramètres dans le corps de la requête:
- "endpoint" - directement le noeud final au noeud Quorum , vous devez vous connecter au noeud.
- "contractAddress" est l'adresse du contrat à partir de laquelle les données seront lues.
Dans mon cas, "endopint" aura un hôte - localhost - mais différents ports pour 3 nœuds de quorum : 22000 (toutes les transactions ont été effectuées à partir de ce nœud), 22001 (sa clé publique a été spécifiée dans les transactions autorisées), 22002 (ne devrait pas avoir accès à informations).
Commençons par une transaction privée (seul un nœud sur le port 22000 devrait pouvoir afficher les informations dans un contrat intelligent).
Demande CURL sur le nœud qui a effectué la transaction:
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" }'
En conséquence, nous avons obtenu ce qui suit:
{"data":{"user":"Private Test User"}}
Cela signifie que le nœud a la possibilité de visualiser les informations dans un contrat intelligent.
Voyons maintenant ce que le nœud du port 22001 nous renvoie. Demande 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": "0x3e2284d92842f781b83cc7e56fbb074ab15f9a90" }'
Super! :
{"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" }'
Super! 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" }'
Super! :
{"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" }'
Super! -. .
Conclusion
, Quorum blockchain Java . , - .
:
- Documentation sur le quorum
- Réseau de test de quorum
- Projet GitHub
- Canal de quorum dans Slack
Merci de votre attention!