Quorum区块链:集成到Java代码中

哈Ha!


对于那些对区块链感兴趣的人来说,这不是什么秘密,除了以太坊比特币Zcash等公共区块链之外,还有一些“企业(私有)”“兄弟”在某些方面要比公共网络更好,但在某些方面他们输给了他们。 在最知名的网络中,我认为您可以命名Quorum (供应商-JP Morgan Chase ), Pantheon (供应商-PegaSys )和Hyperledger (由Linux Foundation管理)。 尽管存在许多公共决策,但企业对私有区块链越来越感兴趣,因为它们能够提供必要的隐私级别,交易速度更快等等。 私有和公共区块链之间的差异以及它们的优缺点不是本文的主题。 如果您有兴趣阅读它,例如,在Medium上有这样的文章。


在本文中,我想告诉您如何使用Quorum区块链来开发支持私有和公共交易的应用程序。 为了演示这些功能,我们将编写一个小型Java / Spring应用程序,该应用程序将接受部署(部署)智能合约,执行交易以及从智能合约读取数据的请求。 实际上,这是本文中将使用的技术堆栈:



有关Quorum的一些常规信息


Quorum是一个在GitHub上具有开源代码的项目,其目的是提供一个区块链,使它不仅可以公开进行交易,还可以以私有模式进行交易。 从技术角度来看, Quorum是一个以太坊升级,它还具有自己的经过修改的Geth客户端,能够进行私人交易。


飞地服务也是一个重要的补充, 飞地服务负责在它们之间存储,加密和分发私人交易。 现在有2种此类飞地服务:


  1. 星座 -用飞地的第一个版本Haskell编写,但是现在它不再发展了,并且很可能在将来被抛弃,取而代之的是新的星座
  2. Tessera是一项用Java编写的新服务,由JP Morgan Chase的开发人员支持,它提供了更多与数据库集成和敏感信息管理的选项(例如,可以与HashiCorp Vault集成以管理秘密)。

至于交易,从普通以太坊的界面来看,变化不大(这很好)。 为了发送私有事务,除了有关事务的常规信息外,还必须指定privateFor参数-这是行的数组 ,这些行是安全节点的公钥。 使用这些密钥, 有效载荷交易被加密,并且payoad区块链内的Tessera节点之间分配。


要更深入地了解Quorum ,如何工作以及如何建立区块链网络,可以在官方网站上找到它(文档的链接以及有关如何启动测试区块链的教程的链接,我将在本文结尾处介绍)。


Java应用开发


作为示例,我将展示一个用Java / Spring编写的小型RESTful API,并将Gradle作为构建和依赖项管理工具,它将智能合约加载到区块链中,执行更改合约状态并从智能合约中读取状态的功能。


在开始开发本身之前,我必须澄清一些事情。 尽管Quorum正式提供了2种交易选项,但我还是倾向于将其分为3种类型:


  1. 公共事务-事务对所有网络参与者(包括有效负载 )完全可见,安全节点不参与处理或存储事务。 Quorum中的公共交易与以太坊网络中的交易没有什么不同;
  2. 许可的交易-交易本质上是私有的,但是对于几个网络参与者,即在公共网络上,我们具有有关交易及其执行状态的信息,但是除了公共网络上的实际有效负载外 ,我们只有一个64位哈希字符串,即通过飞地节点中实际有效载荷的标识符, 飞地节点本身负责在交易的指示方之间签名,加密,存储和分配有效载荷
  3. 私有交易-与允许的交易不同,该交易仅可用于创建该交易的节点,其他网络参与者无法看到有效载荷交易。
    在整篇文章中,我将使用这种分类。

首先,我将向您展示构建文件的外观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' } 

一点解释:


  1. org.web3j.core用于在以太坊网络上进行交易和在Quorum网络上进行公共交易的依赖项
  2. org.web3j.quorumQuorum网络上使用私人交易的依赖
  3. org.web3j.codegen依赖为org.web3j.codegen智能合约生成org.web3j.codegen
  4. generateWrappers-用于从Solidity智能合约生成Java包装器的Gradle任务

接下来,我将向您展示将在本文中使用的智能合约代码: 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; } } 

故意将合同简化,但对于我们的文章而言,合同已足够。 如果您知道Solidity ,那么可以跳过说明:


  • string public user类型为string且名称为user的公共变量。 与Java不同, Solidity自动为公共变量生成一个getter ,因此您无需手动实现它。
  • function writeUser(...) -更改变量值的功能,实际上是setter

为了从智能合约创建Java-wrapper ,您需要将文件以任何名称(例如QuorumDemo.sol放在src/main/solidity/contracts QuorumDemo.sol src/main/solidity/contracts文件夹中。
接下来, 使用以下命令运行Gradle-task generateWrappers


 gradle generateWrappers 

完成此任务后,将在src/main/java/com/github/quorum/component/wrappers创建一个Java包装 src/main/java/com/github/quorum/component/wrappers ,您已经可以在Java代码中使用它。


为了使后端能够签署交易,我们需要能够在发送之前接收有效载荷交易。 为此,最好直接从Java-wrapper类获取它。 在这里,我在包装器内创建了2个方法。 第一种方法只是返回合同的ABI,可用于下载新的智能合同。 第二种方法是形成交易以更改智能合约的状态。 这是这些方法的代码:


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

通过将它们插入生成的Java包装程序中 ,您可以接收事务的有效负载


接下来,我们需要一种方便的方式将交易发送到区块链,最好使用相同的接口进行私人和公共交易。 因此,我创建了一个事务管理器接口及其实现2:


  1. TesseraTransactionManager ,用于发送私人交易
  2. GethTransactionManager ,用于发送公共交易

让我们把它们分开。 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(...) -接口的实现,一种用于在网络上执行事务并处理错误(如果发生)的方法。 返回带有交易结果的对象;
  • EthSendTransaction sendTransaction(...) -一种将交易签名EthSendTransaction sendTransaction(...)链的方法。 返回具有交易状态及其哈希值的对象;
  • TransactionReceipt processResponse(...) -等待事务完成并在执行后返回TransactionReceipt
  • BigInteger getNonce() -从网络返回“ nonce”。

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(...) -接口的实现,一种用于在网络上执行事务并处理错误(如果发生)的方法。 返回带有交易结果的对象;
  • EthSendTransaction sendTransaction(...)是一种调用EthSendTransaction sendTransaction(...)方法以将交易发送到区块链的方法。

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

现在,我将解释哪种方法负责什么。
Mono<ServerResponse> deployContract(...)方法Mono<ServerResponse> deployContract(...) -描述了Mono<ServerResponse> deployContract(...)公共和私有智能合约的一般逻辑。
TransactionManager getTransactionManager(...)方法TransactionManager getTransactionManager(...) -根据事务的类型返回事务管理器的实现对象。 为此,请求参数将包含privateFor参数,该参数是Tessera公钥的字符串数组。
boolean isPrivate(...)方法boolean isPrivate(...) -如果privateFor参数空( 私有交易)或具有公共密钥列表( 允许交易),则返回“ true”。 如果privateFor参数不为空,并且第一个数组元素等于“ public”,则返回“ false”。
APIResponse deployContract(...)方法-将部署事务发送到APIResponse deployContract(...)链。
Mono<ServerResponse> generateResponse(...)方法Mono<ServerResponse> generateResponse(...) -生成一个带有对客户端响应的对象。
Mono<ServerResponse> updateUser(...)方法Mono<ServerResponse> updateUser(...) -描述用于更改智能合约状态的事务的一般逻辑。
APIResponse sendTransaction(...)方法-将状态更改交易发送到APIResponse sendTransaction(...)链。
APIResponse getUser()方法-描述从智能合约读取信息并向客户端返回响应的一般逻辑。
String readUserFromSmartContract(...)方法String readUserFromSmartContract(...) -从智能合约中读取状态并返回结果。


完整的应用程序代码可在GitHub存储库中找到 ,其链接将在本文结尾处。


检查一下


为了测试所有三种交易类型,我编写了测试类(代码在GitHub存储库中)。 为此,我部署了一个具有3个Quorum节点(3个Geth节点+ 3个Tessera节点)的区块链。 3 Quorum节点是验证所有类型的交易所需的最小节点。 如果您想自己尝试,请记住这一点。


公开交易


要使用公共事务执行测试用例,必须运行以下命令:


 gradle test --tests *.PublicTransactionsTests 

该测试用例将发送3个API请求。 第一个是将智能合约部署到区块链,第二个是合约状态的更改,第三个请求是从智能合约中读取信息。 测试的结果是,您将大致看到以下日志(网络上的地址以及事务哈希将有所不同):


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

通常,这些日志表明所有3个操作均成功。 前3个日志-属于部署智能合约的请求,后3个日志-属于交易记录,后2个日志-从智能合约读取信息。
在加载合同的结果中,我们看到的是contract_address ,但是在简单事务的情况下-不,这是很正常的,因为第二次我们不部署合同,而是在现有的智能合同上执行事务。


现在,让我们检查一下Geth我们显示的内容,执行以下命令以连接到客户端的Geth进程IPC界面:


 geth attach /path/to/ipc 

在我们“习惯”该过程之后,您可以完全查看所有必要的信息。 让我们通过执行以下命令来查看部署新智能合约时的TransactionReceipt事务(必须设置事务哈希并从测试日志中获取该哈希):


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

结果,我们看到以下内容:



我们对以下参数感兴趣:


  • “ contractAddress”-如果不是“ null”,那么我们理解这是用于部署智能合约的交易;
  • “状态”-在这种情况下,它等于“ 0x1”-表示交易成功。

让我们看一下交易本身。 通过运行命令:


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

结果:



在这里,我们对以下参数感兴趣:


  • “输入”是有效载荷交易;
  • “ v”-通常,这是ECDSA (数字签名算法)的参数,但现在我们对其他内容感兴趣-变量的含义。 这很重要,因为在公共和私人交易中,它会有所不同。 公共交易通常使用“ 0x1c”(十进制系统中的“ 28”)和“ 0x1b”(十进制系统中的“ 27”),以及“ 0x25”(十进制系统中的“ 37”)和“ 0x26”(十进制系统中的“ 38”系统)-这些是私人交易代码。

您还可以检查其他节点上的信息是否与我们现在看到的信息相同。


现在,您可以查看智能合约的交易状态更改。 运行命令:


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

结果:



我们对以下参数感兴趣:


  • “至”-我们看到交易已经进入了刚刚等待的智能合约;
  • “状态”-等于“ 0x1”,表示交易成功。

交易:



没什么异常,但是您可以检查其他节点上的信息,这很有用。


私人交易


要使用私有事务执行测试用例,必须运行以下命令:


 gradle test --tests *.PrivateTransactionsTests 

与具有公共交易的测试用例一样,该测试用例将部署新的智能合约,执行状态更改交易并从智能合约中的更改中读取信息。


结果,该程序将写入以下日志:


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

与公共交易相比,该更改为privateFor参数-现在它具有空数组的值。
让我们检查TransactionReceipt是否有交易。 团队:


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

结果:



在更改中,与公共交易相比,值得一提的是,您不会看到交易中花费的天然气量-gasUsedcumulativeGasUsed的值为“ 0”。
现在让我们看一下交易本身。 运行命令:


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

结果,我们将看到:



在此交易中值得注意的是:


  1. 正如我在本文开头所提到的,您将在输入字段中看到64行(128个字符)的固定行,而不是实际的有效负载事务。 该行是Tessera存储库中数据的标识符,您可以根据要求向Tessera获取真实数据。
  2. “ v”-代替公共交易中的代码“ 0x1c”或“ 0x1b”,对于私人交易,您将看到“ 0x26”或“ 0x25”。

现在,让我们检查TransactionReceipt和事务本身以更改合同的状态(您已经知道命令)。 结果:




原则上,我们不会从这项私人交易中学到任何新东西。


许可交易


由于这些也是私有交易,因此它们只是私有的,不是针对1个节点,而是针对多个节点,此类交易的结果与私有交易不会有不同。 如果尝试从privateFor中指定的节点和未在privateFor中注册其公钥的节点获取信息,则可以有所作为 (您可以从第一个节点获取信息,而不能从第二个节点获取信息)。
要使用针对多个网络参与者的私有事务(允许的事务)运行测试用例,您需要运行以下命令:


 gradle test --tests *.PermissionTransactionsTests 

Java API日志:


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

Geth客户端中,分别部署新智能合约TransactionReceipt和交易本身的结果:




状态更改事务, TransactionReceipt和事务本身:




HTTP请求


尽管从Geth客户的角度来看,我们看到了公共交易与私人交易有何不同,但这并没有显示出对获取信息的真正限制。 因此,为了向您展示确实有可能限制可以读取交易的节点数量,我将使用CURL发出3个节点以从智能合约中读取信息的多个请求(这些请求将涉及私人交易和许可交易)。
HTTP请求在请求正文中将具有2个参数:


  1. “端点”-直接端点到Quorum节点,您需要连接到该节点。
  2. “ contractAddress”是将从中读取数据的合同的地址。

在我的情况下,“ endopint”将具有一个主机- 本地主机 -但3个Quorum节点具有不同的端口:22000(所有事务均从该节点进行),22001(其公钥在许可的事务中指定),22002(不应访问信息)。


让我们从一个私有交易开始(只有22000端口上的一个节点才能查看智能合约中的信息)。


进行交易的节点上的CURL请求:


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

结果,我们得到了以下内容:


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

这意味着该节点具有查看智能合约中信息的能力。


现在,让我们看看22001端口上的节点返回给我们什么。 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" }' 

太好了! :


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

太好了! 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" }' 

太好了! :


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

太好了! -. .


结论


, Quorum blockchain Java . , - .


:


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

感谢您的关注!

Source: https://habr.com/ru/post/zh-CN462549/


All Articles