Blockchain de quorum: integração ao código Java

Olá Habr!


Para aqueles que estão interessados ​​no assunto de blockchain, não é segredo que, além de blockchains públicas, como Ethereum , Bitcoin , Zcash , etc., também existem seus "irmãos empresariais" (privados) "que são, de certa forma, melhores do que as redes públicas, mas em algo eles perdem para eles. Entre as redes mais conhecidas, acho que você pode nomear Quorum (fornecedor - JP Morgan Chase ), Pantheon (fornecedor - PegaSys ) e Hyperledger (gerenciado pela Linux Foundation ). Apesar de existirem muitas decisões públicas, as empresas estão cada vez mais interessadas em cadeias privadas, porque são capazes de fornecer o nível necessário de privacidade, as transações são mais rápidas e assim por diante. As diferenças entre blockchains privados e públicos, bem como suas vantagens e desvantagens, não são o tópico deste artigo. Se você estiver interessado em ler sobre isso, por exemplo, um artigo sobre o Medium .


Neste artigo, gostaria de dizer como você pode usar o Quorum blockchain para desenvolver seus aplicativos com suporte para transações públicas e privadas. Para demonstrar os recursos, escreveremos um pequeno aplicativo Java / Spring que aceitará solicitações para implantar (implantar) contratos inteligentes, executar transações e ler dados de um contrato inteligente. Na verdade, aqui está a pilha de tecnologia que será usada no artigo:



Algumas informações gerais sobre o Quorum


O Quorum é um projeto com código-fonte aberto no GitHub , cujo objetivo é fornecer uma blockchain que permita realizar transações não apenas publicamente, mas também no modo privado. Do ponto de vista técnico, o Quorum é uma atualização do Ethereum , também possui seu próprio cliente Geth modificado para poder fazer transações privadas.


Uma adição importante também são os serviços de enclave , responsáveis ​​pelo armazenamento, criptografia e distribuição de transações privadas entre si. Agora, existem 2 serviços de enclave :


  1. Constelação - escrita em Haskell, a primeira versão do enclave , mas agora não se desenvolve mais, e provavelmente no futuro será abandonada em favor de uma nova;
  2. Tessera - um novo serviço, escrito em Java , suportado por desenvolvedores do JP Morgan Chase, tem mais opções para integração com o banco de dados e gerenciamento de informações confidenciais (por exemplo, existe uma opção de integração com o HashiCorp Vault para gerenciamento de segredos).

Quanto às transações, do ponto de vista da interface do Ethereum comum , pouco mudou (e isso é bom). Para enviar uma transação privada, além das informações usuais sobre a transação, você também precisa especificar o parâmetro privateFor - esta é a matriz de linhas, e essas linhas são chaves públicas do nó do enclave . Usando essas chaves, as transações de carga útil são criptografadas e a carga útil é distribuída entre os nós do Tessera dentro da blockchain.


Para um conhecimento mais profundo do Quorum , como ele funciona e como aumentar a rede blockchain, você pode encontrá-lo no site oficial (um link para a documentação e um link para o tutorial sobre como lançar um blockchain de teste, deixarei no final do artigo).


Desenvolvimento de Aplicações Java


Como exemplo, mostrarei uma pequena API RESTful escrita em Java / Spring , com Gradle como uma ferramenta de gerenciamento de compilação e dependência que carregará o contrato inteligente no blockchain, executará a função de alterar o estado do contrato e lerá o estado do contrato inteligente.


Antes de iniciar o desenvolvimento, preciso esclarecer uma coisa. Apesar do Quorum oficialmente ter 2 opções de transação, prefiro dividi-las em 3 tipos:


  1. Transações públicas - as transações são totalmente visíveis a todos os participantes da rede (incluindo carga útil ), o nó do enclave não participa do processamento ou armazenamento da transação. As transações públicas no Quorum não são diferentes das transações na rede Ethereum ;
  2. Transações autorizadas - as transações são essencialmente privadas, mas para vários participantes da rede, ou seja, na rede pública, temos informações sobre a transação e o status de sua execução, mas, em vez da carga real na rede pública, temos apenas uma cadeia de hash de 64 bits, que é um identificador para uma carga útil real em um nó enclave , o próprio nó enclave é responsável por assinar, criptografar, armazenar e distribuir carga útil 'entre os participantes da transação especificados;
  3. Transações privadas - diferem das permitidas , pois a transação está disponível apenas para o nó que criou essa transação, outros participantes da rede não podem ver transações de carga útil .
    Vou usar essa classificação ao longo do artigo.

Para começar, mostrarei como será o arquivo de compilação - 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' } 

Um pouco de explicação:


  1. org.web3j.core - dependência para trabalhar com transações na rede Ethereum e transações públicas na rede Quorum
  2. org.web3j.quorum - dependência para trabalhar com transações privadas na rede Quorum
  3. org.web3j.codegen - dependência para gerar org.web3j.codegen para contratos inteligentes do Solidity
  4. generateWrappers - Tarefa gradual para gerar wrappers Java a partir de contratos inteligentes do Solidity

A seguir, mostrarei o código do contrato inteligente que será usado neste artigo: Arquivo 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; } } 

O contrato é intencionalmente simplificado, mas é suficiente para os fins de nosso artigo. Se você conhece Solidity , pode pular a explicação:


  • string public user - uma variável pública do tipo string e o nome user . Diferentemente do Java , o Solidity gera automaticamente um getter para variáveis ​​públicas, portanto você não precisa implementá-lo manualmente.
  • function writeUser(...) - a função de alterar o valor de uma variável, de fato - configurador .

Para criar um Java-wrapper partir de um contrato inteligente, é necessário colocar o arquivo na pasta src/main/solidity/contracts com qualquer nome, por exemplo, QuorumDemo.sol .
Em seguida, execute a tarefa Gradle generateWrappers com o comando:


 gradle generateWrappers 

Depois de concluir esta tarefa, um wrapper Java será criado em src/main/java/com/github/quorum/component/wrappers , com o qual você já pode trabalhar no código Java .


Para que o back-end possa assinar transações, precisamos receber transações de carga útil antes de enviá-las. Para isso, seria bom obtê-lo diretamente da classe Java-wrapper . Aqui eu criei 2 métodos dentro do wrapper. O primeiro método simplesmente retorna a ABI do contrato, que pode ser usada para baixar um novo contrato inteligente. O segundo método é a formação de uma transação para alterar o status de um contrato inteligente. Aqui está o código para estes métodos:


 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); } 

Ao inseri-los no wrapper Java gerado, você pode receber carga útil para transações.


Em seguida, precisamos de uma maneira conveniente de enviar transações para o blockchain, de preferência com a mesma interface para transações públicas e privadas. Portanto, criei uma interface do gerenciador de transações e 2 de sua implementação:


  1. TesseraTransactionManager , para enviar transações privadas
  2. GethTransactionManager , para enviar transações públicas

Vamos separá-los. Código 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(...) - implementação da interface, um método para executar transações na rede e manipular erros, caso ocorram. Retorna um objeto com o resultado de uma transação;
  • EthSendTransaction sendTransaction(...) - um método para assinar e enviar transações para o blockchain. Retorna um objeto com status de transação e seu hash;
  • TransactionReceipt processResponse(...) - um método que aguarda a transação ser concluída e retorna TransactionReceipt após sua execução;
  • BigInteger getNonce() - Retorna "nonce" da rede.

E o código 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(...) - implementação da interface, um método para executar transações na rede e manipular erros, caso ocorram. Retorna um objeto com o resultado de uma transação;
  • EthSendTransaction sendTransaction(...) é um método que chama o método de EthSendTransaction sendTransaction(...) para enviar uma transação para o blockchain.

O manipulador de solicitações que chegam à 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; } } } 

Agora vou explicar quais métodos são responsáveis ​​por quê.
Método Mono<ServerResponse> deployContract(...) - descreve a lógica geral da Mono<ServerResponse> deployContract(...) contrato inteligente, público e privado.
Método TransactionManager getTransactionManager(...) - retorna o objeto de implementação do gerenciador de transações, dependendo do tipo de transação. Para isso, o parâmetro request conterá o parâmetro privateFor , que é uma matriz de cadeias de chaves públicas do Tessera .
boolean isPrivate(...) método boolean isPrivate(...) - retorna "true" se o parâmetro privateFor estiver vazio (transação privada ) ou tiver uma lista de chaves públicas (transação permitida ). Retorna "false" se o parâmetro privateFor não estiver vazio e o primeiro elemento da matriz for igual a "public".
APIResponse deployContract(...) - envia a transação de implantação para o blockchain.
Método Mono<ServerResponse> generateResponse(...) - gera um objeto com uma resposta ao cliente.
Método Mono<ServerResponse> updateUser(...) - descreve a lógica geral da transação para alterar o status do contrato inteligente.
APIResponse sendTransaction(...) - envia uma transação de mudança de estado para o blockchain.
Método APIResponse getUser() - descreve a lógica geral para ler informações de um contrato inteligente e retorna uma resposta ao cliente.
Método de String readUserFromSmartContract(...) - lê o estado do contrato inteligente e retorna o resultado.


O código completo do aplicativo está disponível no repositório GitHub , um link para o qual estará no final deste artigo.


Verifique


Para testar todos os três tipos de transações, escrevi classes de teste (o código está no repositório do GitHub ). Para fazer isso, implantei uma blockchain com 3 nós de quorum (3 nós Geth + 3 nós Tessera ). 3 Nós de quorum são os nós mínimos necessários para verificar todos os tipos de transações. Lembre-se disso se você quiser experimentar.


Transações públicas


Para executar um caso de teste com uma transação pública, você deve executar o seguinte comando:


 gradle test --tests *.PublicTransactionsTests 

Este caso de teste enviará 3 solicitações de API . O primeiro é a implantação do contrato inteligente no blockchain, o segundo é a mudança no status do contrato e o terceiro pedido é a leitura das informações do contrato inteligente. Como resultado do teste, você verá aproximadamente os seguintes logs (os endereços na sua rede serão diferentes, bem como os hashes de transação):


 [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' 

Em geral, esses logs indicam que todas as três operações foram bem-sucedidas. Os 3 primeiros logs - pertencem à solicitação de implantação do contrato inteligente, os 3 próximos logs - pertencem à transação e os 2 últimos - para ler as informações do contrato inteligente.
O fato de que, no resultado do carregamento do contrato, vemos contract_address , mas no caso de uma transação simples - não, isso é bastante normal, já que na segunda vez em que não implantamos o contrato, realizamos a transação em um contrato inteligente existente.


Agora vamos verificar o que Geth nos mostra, execute o seguinte comando para conectar-se à interface IPC do processo Geth do cliente:


 geth attach /path/to/ipc 

Depois de nos acostumarmos com o processo, você pode revisar completamente todas as informações necessárias. Vejamos a transação TransactionReceipt na implantação de um novo contrato inteligente executando o comando (o hash da transação deve ser configurado e obtido nos logs de teste):


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

Como resultado, vemos o seguinte:



Estamos interessados ​​nos seguintes parâmetros:


  • "contractAddress" - se não for "null", entendemos que esta é uma transação para a implantação de um contrato inteligente;
  • "status" - nesse caso, é igual a "0x1" - o que significa que a transação foi bem-sucedida.

E vamos olhar para a transação em si. Executando o comando:


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

Resultado:



Aqui estamos interessados ​​nos seguintes parâmetros:


  • "input" é uma transação de carga útil ;
  • "v" - em geral, este é um parâmetro para ECDSA , o algoritmo de assinatura digital, mas agora estamos interessados ​​em outra coisa - o significado da variável. É importante porque em transações públicas e privadas será diferente. "0x1c" ("28" no sistema decimal) e "0x1b" ("27" no sistema decimal) são típicos para transações públicas e "0x25" ("37" no sistema decimal) e "0x26" ("38" em decimal sistema) - estes são códigos de transação privada.

Você também pode verificar se em outros nós as informações não diferem do que vimos agora.


Agora você pode visualizar as alterações no status da transação do contrato inteligente. Execute o comando:


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

Resultado:



Estamos interessados ​​nos seguintes parâmetros:


  • "para" - vemos que a transação foi para o contrato inteligente esperado;
  • "status" - é igual a "0x1", o que significa que a transação foi bem-sucedida.

Transação:



Nada incomum, mas você pode verificar as informações em outros nós, isso é útil.


Transações privadas


Para executar um caso de teste com uma transação privada, você deve executar o seguinte comando:


 gradle test --tests *.PrivateTransactionsTests 

Como no caso de teste com transações públicas, esse caso de teste implementará um novo contrato inteligente, executará uma transação de alteração de estado e lerá as informações da alteração no contrato inteligente.


Como resultado, o programa gravará os seguintes logs:


 [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' 

A alteração, em comparação com transações públicas, é o parâmetro privateFor - agora ele tem o valor de uma matriz vazia.
Vamos verificar TransactionReceipt para uma transação. Equipa:


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

Resultado:



Das alterações, comparadas às transações públicas, vale dizer que você não verá a quantidade de gás gasta na transação - gasUsed e cumulativeGasUsed têm o valor "0".
Agora vamos ver a transação em si. Execute o comando:


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

Como resultado, veremos isso:



O que vale a pena notar nesta transação:


  1. Como mencionei no início deste artigo, em vez de uma transação de carga útil real, você verá uma linha fixa de 64 bytes (128 caracteres) no campo de entrada . Esta linha é o identificador dos dados no repositório do Tessera . Você pode obter dados reais mediante solicitação ao Tessera .
  2. "v" - em vez dos códigos "0x1c" ou "0x1b", como nas transações públicas, nas transações privadas, você verá "0x26" ou "0x25".

Agora vamos verificar o TransactionReceipt e a própria transação para alterar o status do contrato (você já conhece os comandos). Resultado:




Em princípio, não aprenderemos nada de novo com esta transação privada.


Transações permitidas


Como essas também são transações privadas, elas são simplesmente privadas, não para 1 nó, mas para várias, os resultados dessas transações não serão diferentes das transações particulares. Você pode fazer a diferença se tentar obter informações de um nó especificado em privateFor e de um nó cuja chave pública não esteja registrada em privateFor (você pode obter informações do primeiro nó e não do segundo).
Para executar um caso de teste com transações privadas para vários participantes da rede (transações autorizadas), é necessário executar o seguinte comando:


 gradle test --tests *.PermissionTransactionsTests 

Logs da 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' 

Os resultados no cliente Geth , na implantação do novo contrato inteligente, TransactionReceipt e a própria transação, respectivamente:




E a transação de mudança de estado, TransactionReceipt e a própria transação:




Solicitações HTTP


Apesar de termos visto como as transações públicas diferem das transações privadas do ponto de vista do cliente Geth , isso não mostra uma restrição real na obtenção de informações. Portanto, para mostrar que é realmente possível limitar o número de nós que podem ler sua transação, farei várias solicitações usando CURL para 3 nós para ler informações do contrato inteligente (as solicitações se relacionarão a transações privadas e com permissão ).
Solicitações HTTP terão 2 parâmetros no corpo da solicitação:


  1. "endpoint" - endpoint diretamente para o nó Quorum , é necessário conectar-se ao nó.
  2. "contractAddress" é o endereço do contrato a partir do qual os dados serão lidos.

No meu caso, "endopint" terá um host - localhost - mas portas diferentes para três nós do Quorum : 22000 (todas as transações foram feitas nesse nó), 22001 (sua chave pública foi especificada em transações autorizadas), 22002 (não deve ter acesso a informações).


Vamos começar com uma transação privada (apenas um nó na porta 22000 deve poder visualizar informações em um contrato inteligente).


Solicitação CURL no nó que fez a transação:


 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" }' 

Como resultado, obtivemos o seguinte:


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

Isso significa que o nó tem a capacidade de visualizar informações em um contrato inteligente.


Agora vamos ver o que o nó na porta 22001 retorna para nós. Solicitação de 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" }' 

Ótimo! :


 {"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" }' 

Ótimo! 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" }' 

Ótimo! :


 {"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" }' 

Ótimo! -. .


Conclusão


, Quorum blockchain Java . , - .


:


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

Obrigado pela atenção!

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


All Articles