Quorum blockchain: integration into Java code

Hello, Habr!







It's not a secret for those who are interested in the topic of blockchain that in addition to public blockchains, such as Ethereum , Bitcoin , Zcash , etc., there are also their "enterprise (private)" "brothers" who are in some ways better than public networks, but in something they lose to them. Among the most well-known networks, I think you can name Quorum (vendor - JP Morgan Chase ), Pantheon (vendor - PegaSys ) and Hyperledger (managed by The Linux Foundation ). Despite the fact that there are quite a lot of public decisions, businesses are increasingly interested in private blockchains because they are able to provide the necessary level of privacy, transactions are faster, and so on. Differences between private and public blockchains, as well as their advantages and disadvantages, are not the topic of this article. If you are interested in reading about it, that is, for example, such an article on Medium .







In this article, I would like to tell you how you can use Quorum blockchain to develop your applications with support for private and public transactions. To demonstrate the capabilities, we will write a small Java / Spring application that will accept requests to deploy (deploy) smart contracts, execute transactions and read data from a smart contract. Actually, here is the technology stack that will be used in the article:









Some general information about Quorum



Quorum is a project with open source code on GitHub , the purpose of which is to provide a blockchain that would make it possible to carry out transactions not only publicly but also in private mode too. From a technical point of view, Quorum is an Ethereum upgrade, it also has its own modified Geth client to be able to do private transactions.







An important addition is also enclave services, which are responsible for the storage, encryption and distribution of private transactions among themselves. Now there are 2 such enclave services:







  1. Constellation - written in Haskell, the first version of enclave , but now it’s not developing anymore, and most likely in the future it will be abandoned in favor of a new one;
  2. Tessera is a new service, written in Java , supported by developers from JP Morgan Chase, has more options for integration with the database and management of sensitive information (for example, there is an integration option with HashiCorp Vault for managing secrets).


As for transactions, from the point of view of the interface of ordinary Ethereum, not much has changed (and this is good). In order to send a private transaction, in addition to the usual information about the transaction, you must also specify the privateFor parameter - this is the array of lines, and these lines are public keys of the enclave node. Using these keys, payload transactions are encrypted and payoad is distributed between Tessera nodes inside the blockchain.







For a deeper acquaintance with Quorum , how it works, and how to raise the blockchain network, you can find it on the official website (a link to the documentation, as well as a link to the tutorial on how to launch a test blockchain, I will leave at the end of the article).







Java Application Development



As an example, I will show a small RESTful API written in Java / Spring , with Gradle as a build and dependency management tool that will load the smart contract into the blockchain, perform the function of changing the state of the contract and read the state from the smart contract.







Before starting the development itself, I have to clarify something. Despite the fact that Quorum officially has 2 transaction options, I prefer to divide them into 3 types:







  1. Public transactions - transactions are fully visible to all network participants (including payload ), the enclave node does not participate in either processing or storing the transaction. Public transactions in Quorum are no different from transactions in the Ethereum network;
  2. Permissioned transactions - transactions are essentially private, but for several network participants, that is, on the public network, we have information about the transaction and the status of its execution, but instead of real payload on the public network, we only have a 64-bit hash string, which is an identifier for a real payload in an enclave node, the enclave node itself is responsible for signing, encrypting, storing and distributing payload 'between the specified parties to the transaction;
  3. Private transactions - differs from permissioned in that the transaction is available only for the node that created this transaction, other network participants cannot see payload transactions.

    I will use this classification throughout the article.


To start, I'll show you what the build file will look like - 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' }
      
      





A little explanation:







  1. org.web3j.core



    - dependency for working with transactions on the Ethereum network and public transactions on the Quorum network
  2. org.web3j.quorum



    - dependency for working with private transactions on the Quorum network
  3. org.web3j.codegen



    - dependency for wrapper generation for Solidity smart contracts
  4. generateWrappers - Gradle-task for generating Java- wrappers from Solidity smart contracts


Next, I will show you the smart contract code that will be used in this article: QuorumDemo.sol



file:







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





The contract is intentionally made simple, but it is enough for the purposes of our article. If you know Solidity , then you can skip the explanation:









In order to create a Java-wrapper



from a smart contract, you need to put the file in the src/main/solidity/contracts



folder with any name, for example QuorumDemo.sol



.

Next, run Gradle-task generateWrappers with the command:







 gradle generateWrappers
      
      





After completing this task, a Java-wrapper will be created at src/main/java/com/github/quorum/component/wrappers



, which you can already work with in Java code.







In order for the backend to be able to sign transactions, we need to be able to receive payload transactions before we send it. For this, it would be nice to get it directly from the Java-wrapper class. Here I created 2 methods inside the wrapper. The first method simply returns the ABI of the contract, which can be used to download a new smart contract. The second method is the formation of a transaction to change the status of a smart contract. Here is the code for these methods:







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





By inserting them into the generated Java-wrapper , you can receive payload for transactions.







Next, we need a convenient way to send transactions to the blockchain, preferably with the same interface for private and public transactions. Therefore, I created the interface of the transaction manager and 2 of its implementation:







  1. TesseraTransactionManager



    , for sending private transactions
  2. GethTransactionManager



    , for sending public transactions


Let's take them apart. 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(); } }
      
      







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







The handler for requests that come to the 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; } } }
      
      





Now I will explain what methods are responsible for what.

Mono<ServerResponse> deployContract(...)



method Mono<ServerResponse> deployContract(...)



- describes the general logic of Mono<ServerResponse> deployContract(...)



smart contract, both public and private.

TransactionManager getTransactionManager(...)



method TransactionManager getTransactionManager(...)



- returns the implementation object of the transaction manager depending on the type of transaction. To do this, the request parameter will contain the privateFor parameter, which is an array of strings of Tessera public keys.

boolean isPrivate(...)



method boolean isPrivate(...)



- returns "true" if the privateFor parameter is either empty ( private transaction) or has a list of public keys ( permissioned transaction). Returns "false" if the privateFor parameter is not empty, and the first array element is equal to "public".

APIResponse deployContract(...)



method - sends the deploy transaction to the blockchain.

Mono<ServerResponse> generateResponse(...)



method Mono<ServerResponse> generateResponse(...)



- generates an object with a response to the client.

Mono<ServerResponse> updateUser(...)



method Mono<ServerResponse> updateUser(...)



- describes the general logic of the transaction to change the status of the smart contract.

APIResponse sendTransaction(...)



method - sends a state change transaction to the blockchain.

APIResponse getUser()



method - describes the general logic for reading information from a smart contract and returns a response to the client.

String readUserFromSmartContract(...)



method String readUserFromSmartContract(...)



- reads the state from the smart contract and returns the result.







The full application code is available in the GitHub repository, a link to which will be at the end of this article.







Check



To test all 3 types of transactions, I wrote test classes (the code is in the GitHub repository). To do this, I deployed a blockchain with 3 Quorum nodes (3 Geth nodes + 3 Tessera nodes). 3 Quorum nodes are the minimum nodes required to verify all types of transactions. Keep this in mind if you want to try it yourself.







Public transactions



To execute a test case with a public transaction, you must run the following command:







 gradle test --tests *.PublicTransactionsTests
      
      





This test case will send 3 API requests. The first is to deploy a smart contract to the blockchain, the second is the change in the status of the contract and the third request is the reading of information from the smart contract. As a result of the test, you will see approximately the following logs (addresses on your network will differ, as well as transaction hashes):







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





In general, these logs indicate that all 3 operations were successful. The first 3 logs - belong to the request for the deployment of the smart contract, the next 3 logs - belong to the transaction, and the last 2 - to read the information from the smart contract.

The fact that in the result of loading the contract we see contract_address , but in the case of a simple transaction - no, this is quite normal, because for the second time we do not deploy the contract, but carry out the transaction on an existing smart contract.







Now let's check what Geth shows us , execute the following command to connect to the client’s Geth process IPC interface:







 geth attach /path/to/ipc
      
      





After we β€œgot used” to the process, you can completely review all the necessary information. Let's look at the TransactionReceipt



transaction on the deployment of a new smart contract by executing the command (the transaction hash must be set up and taken from the test logs):







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





As a result, we see the following:













We are interested in the following parameters:









And let's look at the transaction itself. By running the command:







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





Result:













Here we are interested in the following parameters:









You can also check that on other nodes the information does not differ from what we saw now.







Now you can view the transaction status changes of the smart contract. Run the command:







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





Result:













We are interested in the following parameters:









Transaction:













Nothing unusual, but you can check the information on other nodes, this is useful.







Private Transactions



To execute a test case with a private transaction, you must run the following command:







 gradle test --tests *.PrivateTransactionsTests
      
      





As in the test case with public transactions, this test case will deploy a new smart contract, execute a state change transaction and read information from the change in the smart contract.







As a result, the program will write the following 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'
      
      





The change, compared with public transactions, is the privateFor parameter - now it has the value of an empty array.

Let's check TransactionReceipt



for a transaction. Team:







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





Result:













Of the changes, compared with public transactions, it is worth saying that you will not see the amount of gas spent on the transaction - gasUsed and cumulativeGasUsed have a value of "0".

Now let's look at the transaction itself. Run the command:







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





As a result, we will see this:













What is worth noting in this transaction:







  1. As I mentioned at the beginning of this article, instead of a real payload transaction, you will see a fixed line of 64 bytes (128 characters) in the input field. This line is the identifier for the data in the Tessera storage, you can get real data upon request to Tessera .
  2. "v" - instead of the codes "0x1c" or "0x1b" as in public transactions, for private transactions you will see "0x26" or "0x25".


Now let's check the TransactionReceipt



and the transaction itself to change the state of the contract (you already know the commands). Result:



















In principle, we will not learn anything new from this private transaction.







Permissioned Transactions



Since these are also private transactions, they are simply private, not for 1 node, but for several, the results of such transactions will not differ from private transactions. You can make a difference if you try to get information from a node that was specified in privateFor and from a node whose public key is not registered in privateFor (you can get information from the first node and cannot from the second).

To run a test case with transactions private for several network participants (permissioned transactions), you need to run the following command:







 gradle test --tests *.PermissionTransactionsTests
      
      





Java API Logs:







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





The results in the Geth client, on the deployment of the new smart contract, TransactionReceipt



and the transaction itself, respectively:



















And the state change transaction, TransactionReceipt



and the transaction itself:



















HTTP requests



Despite the fact that we saw how public transactions differ from private ones from the point of view of the Geth client, this does not show a real restriction on obtaining information. Therefore, in order to show you that it is really possible to limit the number of nodes that can read your transaction, I will make several requests using CURL for 3 nodes to read information from the smart contract (the requests will concern private and persmissioned transactions).

HTTP requests will have 2 parameters in the request body:







  1. "endpoint" - directly endpoint to the Quorum node, you need to connect to the node.
  2. "contractAddress" - the address of the contract from which the data will be read.


In my case, "endopint" will have one host - localhost - but different ports for 3 Quorum nodes: 22000 (all transactions were made from this node), 22001 (its public key was specified in permissioned transactions), 22002 (should not have access to information).







Let's start with a private transaction (only a node on port 22000 should be able to view information in a smart contract).







CURL request on the node that made the 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" }'
      
      





As a result, we got the following:







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





This means that the node has the ability to view information in a smart contract.







Now let's see what the node on the 22001 port returns to us. CURL request:







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





Fine! As a result, we received the following error message:







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





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





Result:







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





Fine! :







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





Fine! -. .







Conclusion



, Quorum blockchain Java . , - .







:







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


Thanks for attention!








All Articles