diff --git a/acceptance-tests/build.gradle b/acceptance-tests/build.gradle index 1a6e05e91b..a492f153b6 100644 --- a/acceptance-tests/build.gradle +++ b/acceptance-tests/build.gradle @@ -25,8 +25,10 @@ dependencies { testImplementation project(':ethereum:blockcreation') testImplementation project(':ethereum:jsonrpc') testImplementation project(':ethereum:permissioning') + testImplementation project(':ethereum:rlp') testImplementation project(':metrics') testImplementation project(':pantheon') + testImplementation project(':testutil') testImplementation project(':util') testImplementation project(path: ':ethereum:core', configuration: 'testSupportArtifacts') @@ -35,6 +37,7 @@ dependencies { testImplementation 'io.reactivex.rxjava2:rxjava' testImplementation 'io.vertx:vertx-core' testImplementation 'junit:junit' + testImplementation 'net.consensys:orion' testImplementation 'org.apache.logging.log4j:log4j-api' testImplementation 'org.assertj:assertj-core' testImplementation 'org.awaitility:awaitility' diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Eth.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Eth.java index 01a85e70dd..40dcbff272 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Eth.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Eth.java @@ -12,7 +12,6 @@ */ package tech.pegasys.pantheon.tests.acceptance.dsl.jsonrpc; -import tech.pegasys.pantheon.ethereum.core.Hash; import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition; import tech.pegasys.pantheon.tests.acceptance.dsl.condition.eth.ExpectEthAccountsException; import tech.pegasys.pantheon.tests.acceptance.dsl.condition.eth.ExpectEthGetTransactionReceiptIsAbsent; @@ -42,9 +41,9 @@ public class Eth { return new ExpectEthAccountsException(transactions.accounts(), expectedMessage); } - public Condition expectSuccessfulTransactionReceipt(final Hash transactionHash) { + public Condition expectSuccessfulTransactionReceipt(final String transactionHash) { return new ExpectSuccessfulEthGetTransactionReceipt( - transactions.getTransactionReceipt(transactionHash.toString())); + transactions.getTransactionReceipt(transactionHash)); } public Condition expectNoTransactionReceipt(final String transactionHash) { diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java index 2c65244829..df789b1839 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java @@ -19,6 +19,7 @@ import tech.pegasys.pantheon.controller.KeyPairUtil; import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; import tech.pegasys.pantheon.ethereum.core.Address; import tech.pegasys.pantheon.ethereum.core.MiningParameters; +import tech.pegasys.pantheon.ethereum.core.PrivacyParameters; import tech.pegasys.pantheon.ethereum.core.Util; import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration; import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration; @@ -29,6 +30,7 @@ import tech.pegasys.pantheon.tests.acceptance.dsl.httptransaction.HttpRequestFac import tech.pegasys.pantheon.tests.acceptance.dsl.httptransaction.HttpTransaction; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.AdminJsonRpcRequestFactory; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.CliqueJsonRpcRequestFactory; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.EeaJsonRpcRequestFactory; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.IbftJsonRpcRequestFactory; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.JsonRequestFactories; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.PermissioningJsonRpcRequestFactory; @@ -79,6 +81,7 @@ public class PantheonNode implements Node, NodeConfiguration, RunnableNode, Auto private final String name; private final MiningParameters miningParameters; + private final PrivacyParameters privacyParameters; private final JsonRpcConfiguration jsonRpcConfiguration; private final WebSocketConfiguration webSocketConfiguration; private final MetricsConfiguration metricsConfiguration; @@ -98,6 +101,7 @@ public class PantheonNode implements Node, NodeConfiguration, RunnableNode, Auto public PantheonNode( final String name, final MiningParameters miningParameters, + final PrivacyParameters privacyParameters, final JsonRpcConfiguration jsonRpcConfiguration, final WebSocketConfiguration webSocketConfiguration, final MetricsConfiguration metricsConfiguration, @@ -114,6 +118,7 @@ public class PantheonNode implements Node, NodeConfiguration, RunnableNode, Auto this.p2pPort = p2pPort; this.name = name; this.miningParameters = miningParameters; + this.privacyParameters = privacyParameters; this.jsonRpcConfiguration = jsonRpcConfiguration; this.webSocketConfiguration = webSocketConfiguration; this.metricsConfiguration = metricsConfiguration; @@ -234,6 +239,7 @@ public class PantheonNode implements Node, NodeConfiguration, RunnableNode, Auto new IbftJsonRpcRequestFactory(web3jService), new PermissioningJsonRpcRequestFactory(web3jService), new AdminJsonRpcRequestFactory(web3jService), + new EeaJsonRpcRequestFactory(web3jService), websocketService); } @@ -292,6 +298,10 @@ public class PantheonNode implements Node, NodeConfiguration, RunnableNode, Auto this.token = token; } + public KeyPair getKeyPair() { + return keyPair; + } + private void checkIfWebSocketEndpointIsAvailable(final String url) { final WebSocketClient webSocketClient = new WebSocketClient(URI.create(url)); // Web3j implementation always invoke the listener (even when one hasn't been set). We are using @@ -424,6 +434,10 @@ public class PantheonNode implements Node, NodeConfiguration, RunnableNode, Auto return miningParameters; } + public PrivacyParameters getPrivacyParameters() { + return privacyParameters; + } + public boolean isDevMode() { return devMode; } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ProcessPantheonNodeRunner.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ProcessPantheonNodeRunner.java index d0dedc544a..5669b00093 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ProcessPantheonNodeRunner.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ProcessPantheonNodeRunner.java @@ -71,6 +71,16 @@ public class ProcessPantheonNodeRunner implements PantheonNodeRunner { params.add(node.getMiningParameters().getCoinbase().get().toString()); } + if (node.getPrivacyParameters().isEnabled()) { + params.add("--privacy-enabled"); + params.add("--privacy-url"); + params.add(node.getPrivacyParameters().getUrl()); + params.add("--privacy-public-key-file"); + params.add(node.getPrivacyParameters().getPublicKeyFile().getAbsolutePath()); + params.add("--privacy-precompiled-address"); + params.add(String.valueOf(node.getPrivacyParameters().getPrivacyAddress())); + } + params.add("--bootnodes"); params.add(String.join(",", node.bootnodes().toString())); diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ThreadPantheonNodeRunner.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ThreadPantheonNodeRunner.java index b5c0496f6b..cab8573657 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ThreadPantheonNodeRunner.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ThreadPantheonNodeRunner.java @@ -20,7 +20,6 @@ import tech.pegasys.pantheon.cli.EthNetworkConfig; import tech.pegasys.pantheon.cli.PantheonControllerBuilder; import tech.pegasys.pantheon.controller.KeyPairUtil; import tech.pegasys.pantheon.controller.PantheonController; -import tech.pegasys.pantheon.ethereum.core.PrivacyParameters; import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration; import tech.pegasys.pantheon.metrics.MetricsSystem; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; @@ -69,10 +68,10 @@ public class ThreadPantheonNodeRunner implements PantheonNodeRunner { .ethNetworkConfig(ethNetworkConfig) .syncWithOttoman(false) .miningParameters(node.getMiningParameters()) + .privacyParameters(node.getPrivacyParameters()) .devMode(node.isDevMode()) .nodePrivateKeyFile(KeyPairUtil.getDefaultKeyFile(node.homeDirectory())) .metricsSystem(noOpMetricsSystem) - .privacyParameters(PrivacyParameters.noPrivacy()) .build(); } catch (final IOException e) { throw new RuntimeException("Error building PantheonController", e); diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonFactoryConfiguration.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonFactoryConfiguration.java index ad546289a8..81a9324244 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonFactoryConfiguration.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonFactoryConfiguration.java @@ -13,6 +13,7 @@ package tech.pegasys.pantheon.tests.acceptance.dsl.node.factory; import tech.pegasys.pantheon.ethereum.core.MiningParameters; +import tech.pegasys.pantheon.ethereum.core.PrivacyParameters; import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration; import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration; import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration; @@ -25,6 +26,7 @@ class PantheonFactoryConfiguration { private final String name; private final MiningParameters miningParameters; + private final PrivacyParameters privacyParameters; private final JsonRpcConfiguration jsonRpcConfiguration; private final WebSocketConfiguration webSocketConfiguration; private final MetricsConfiguration metricsConfiguration; @@ -38,6 +40,7 @@ class PantheonFactoryConfiguration { PantheonFactoryConfiguration( final String name, final MiningParameters miningParameters, + final PrivacyParameters privacyParameters, final JsonRpcConfiguration jsonRpcConfiguration, final WebSocketConfiguration webSocketConfiguration, final MetricsConfiguration metricsConfiguration, @@ -49,6 +52,7 @@ class PantheonFactoryConfiguration { final boolean isBootnode) { this.name = name; this.miningParameters = miningParameters; + this.privacyParameters = privacyParameters; this.jsonRpcConfiguration = jsonRpcConfiguration; this.webSocketConfiguration = webSocketConfiguration; this.metricsConfiguration = metricsConfiguration; @@ -68,6 +72,10 @@ class PantheonFactoryConfiguration { return miningParameters; } + public PrivacyParameters getPrivacyParameters() { + return privacyParameters; + } + public JsonRpcConfiguration getJsonRpcConfiguration() { return jsonRpcConfiguration; } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonFactoryConfigurationBuilder.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonFactoryConfigurationBuilder.java index 58c1b8d2db..16dd5127eb 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonFactoryConfigurationBuilder.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonFactoryConfigurationBuilder.java @@ -16,7 +16,9 @@ import static java.util.Collections.singletonList; import tech.pegasys.pantheon.ethereum.core.MiningParameters; import tech.pegasys.pantheon.ethereum.core.MiningParametersTestBuilder; +import tech.pegasys.pantheon.ethereum.core.PrivacyParameters; import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration; +import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApis; import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration; import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration; import tech.pegasys.pantheon.metrics.prometheus.MetricsConfiguration; @@ -32,6 +34,7 @@ public class PantheonFactoryConfigurationBuilder { private String name; private MiningParameters miningParameters = new MiningParametersTestBuilder().enabled(false).build(); + private PrivacyParameters privacyParameters = PrivacyParameters.noPrivacy(); private JsonRpcConfiguration jsonRpcConfiguration = JsonRpcConfiguration.createDefault(); private WebSocketConfiguration webSocketConfiguration = WebSocketConfiguration.createDefault(); private MetricsConfiguration metricsConfiguration = MetricsConfiguration.createDefault(); @@ -72,6 +75,14 @@ public class PantheonFactoryConfigurationBuilder { return this; } + public PantheonFactoryConfigurationBuilder enablePrivateTransactions( + final PrivacyParameters privacyParameters) { + this.jsonRpcConfiguration.addRpcApi(RpcApis.EEA); + this.privacyParameters = privacyParameters; + this.privacyParameters.setEnabled(true); + return this; + } + public PantheonFactoryConfigurationBuilder jsonRpcAuthenticationEnabled() throws URISyntaxException { final String authTomlPath = @@ -156,6 +167,7 @@ public class PantheonFactoryConfigurationBuilder { return new PantheonFactoryConfiguration( name, miningParameters, + privacyParameters, jsonRpcConfiguration, webSocketConfiguration, metricsConfiguration, diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonNodeFactory.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonNodeFactory.java index 1fda4335bf..4acf16c7c8 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonNodeFactory.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonNodeFactory.java @@ -21,6 +21,7 @@ import static tech.pegasys.pantheon.consensus.ibft.jsonrpc.IbftRpcApis.IBFT; import tech.pegasys.pantheon.consensus.clique.CliqueExtraData; import tech.pegasys.pantheon.consensus.ibft.IbftExtraData; import tech.pegasys.pantheon.ethereum.core.Address; +import tech.pegasys.pantheon.ethereum.core.PrivacyParameters; import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration; import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApi; import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApis; @@ -56,6 +57,7 @@ public class PantheonNodeFactory { new PantheonNode( config.getName(), config.getMiningParameters(), + config.getPrivacyParameters(), config.getJsonRpcConfiguration(), config.getWebSocketConfiguration(), config.getMetricsConfiguration(), @@ -81,6 +83,18 @@ public class PantheonNodeFactory { .build()); } + public PantheonNode createPrivateTransactionEnabledMinerNode( + final String name, final PrivacyParameters privacyParameters) throws IOException { + return create( + new PantheonFactoryConfigurationBuilder() + .setName(name) + .miningEnabled() + .jsonRpcEnabled() + .enablePrivateTransactions(privacyParameters) + .webSocketEnabled() + .build()); + } + public PantheonNode createMinerNodeWithCustomRefreshDelay( final String name, final Long refreshDelay) throws IOException { diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/EeaJsonRpcRequestFactory.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/EeaJsonRpcRequestFactory.java new file mode 100644 index 0000000000..fc561cd56a --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/EeaJsonRpcRequestFactory.java @@ -0,0 +1,48 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.tests.acceptance.dsl.transaction; + +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.ResponseTypes.PrivateTransactionReceiptResponse; + +import java.util.Collections; + +import org.assertj.core.util.Lists; +import org.web3j.protocol.Web3jService; +import org.web3j.protocol.core.Request; + +public class EeaJsonRpcRequestFactory { + + private final Web3jService web3jService; + + public EeaJsonRpcRequestFactory(final Web3jService web3jService) { + this.web3jService = web3jService; + } + + public Request + eeaSendRawTransaction(final String signedTransactionData) { + return new Request<>( + "eea_sendRawTransaction", + Collections.singletonList(signedTransactionData), + web3jService, + org.web3j.protocol.core.methods.response.EthSendTransaction.class); + } + + public Request eeaGetTransactionReceipt( + final String txHash, final String publicKey) { + return new Request<>( + "eea_getTransactionReceipt", + Lists.newArrayList(txHash, publicKey), + web3jService, + PrivateTransactionReceiptResponse.class); + } +} diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/JsonRequestFactories.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/JsonRequestFactories.java index cd4d176eb4..4cb6812a17 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/JsonRequestFactories.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/JsonRequestFactories.java @@ -24,6 +24,7 @@ public class JsonRequestFactories { private final IbftJsonRpcRequestFactory ibft; private final PermissioningJsonRpcRequestFactory perm; private final AdminJsonRpcRequestFactory admin; + private final EeaJsonRpcRequestFactory eea; private final Optional websocketService; public JsonRequestFactories( @@ -32,12 +33,14 @@ public class JsonRequestFactories { final IbftJsonRpcRequestFactory ibft, final PermissioningJsonRpcRequestFactory perm, final AdminJsonRpcRequestFactory admin, + final EeaJsonRpcRequestFactory eea, final Optional websocketService) { this.netEth = netEth; this.clique = clique; this.ibft = ibft; this.perm = perm; this.admin = admin; + this.eea = eea; this.websocketService = websocketService; } @@ -65,6 +68,10 @@ public class JsonRequestFactories { return admin; } + public EeaJsonRpcRequestFactory eea() { + return eea; + } + public void shutdown() { netEth.shutdown(); websocketService.ifPresent(WebSocketService::close); diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/ResponseTypes.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/ResponseTypes.java index b810e266ff..2846994a2c 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/ResponseTypes.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/ResponseTypes.java @@ -39,4 +39,39 @@ public class ResponseTypes { public static class RemoveNodeResponse extends Response {} public static class GetNodesWhitelistResponse extends Response> {} + + public static class PrivateTransactionReceiptResponse + extends Response {} + + public static class PrivateTransactionReceipt { + private String contractAddress; + private String from; + private String to; + + public PrivateTransactionReceipt() {} + + public String getContractAddress() { + return contractAddress; + } + + public void setContractAddress(final String contractAddress) { + this.contractAddress = contractAddress; + } + + public String getFrom() { + return from; + } + + public void setFrom(final String from) { + this.from = from; + } + + public String getTo() { + return to; + } + + public void setTo(final String to) { + this.to = to; + } + } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/Transactions.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/Transactions.java index 4ffc42051a..01533a9e29 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/Transactions.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/Transactions.java @@ -16,7 +16,10 @@ import tech.pegasys.pantheon.tests.acceptance.dsl.account.Account; import tech.pegasys.pantheon.tests.acceptance.dsl.account.Accounts; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.account.TransferTransaction; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.account.TransferTransactionSet; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eea.EeaGetTransactionReceiptTransaction; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eea.EeaSendRawTransactionTransaction; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eth.EthGetTransactionCountTransaction; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eth.EthGetTransactionReceiptTransaction; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.perm.PermAddAccountsToWhitelistTransaction; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.perm.PermAddNodeTransaction; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.perm.PermGetAccountsWhitelistTransaction; @@ -54,6 +57,11 @@ public class Transactions { return new TransferTransaction(sender, recipient, String.valueOf(amount), Unit.ETHER, nonce); } + public EeaSendRawTransactionTransaction createPrivateRawTransaction( + final String signedRawPrivateTransaction) { + return new EeaSendRawTransactionTransaction(signedRawPrivateTransaction); + } + public TransferTransactionSet createIncrementalTransfers( final Account sender, final Account recipient, final int etherAmount) { final List transfers = new ArrayList<>(); @@ -87,6 +95,15 @@ public class Transactions { return new EthGetTransactionCountTransaction(accountAddress); } + public EthGetTransactionReceiptTransaction getTransactionReceipt(final String transactionHash) { + return new EthGetTransactionReceiptTransaction(transactionHash); + } + + public EeaGetTransactionReceiptTransaction getPrivateTransactionReceipt( + final String transactionHash, final String publicKey) { + return new EeaGetTransactionReceiptTransaction(transactionHash, publicKey); + } + public PermAddNodeTransaction addNodesToWhitelist(final List enodeList) { return new PermAddNodeTransaction(enodeList); } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eea/EeaGetTransactionReceiptTransaction.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eea/EeaGetTransactionReceiptTransaction.java new file mode 100644 index 0000000000..d6f5ae326e --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eea/EeaGetTransactionReceiptTransaction.java @@ -0,0 +1,46 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eea; + +import static org.assertj.core.api.Assertions.assertThat; + +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.JsonRequestFactories; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.ResponseTypes.PrivateTransactionReceipt; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.ResponseTypes.PrivateTransactionReceiptResponse; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction; + +import java.io.IOException; + +public class EeaGetTransactionReceiptTransaction implements Transaction { + + private final String txHash; + private final String publicKey; + + public EeaGetTransactionReceiptTransaction(final String txHash, final String publicKey) { + this.txHash = txHash; + this.publicKey = publicKey; + } + + @Override + public PrivateTransactionReceipt execute(final JsonRequestFactories node) { + try { + final PrivateTransactionReceiptResponse result = + node.eea().eeaGetTransactionReceipt(txHash, publicKey).send(); + assertThat(result).isNotNull(); + assertThat(result.hasError()).isFalse(); + return result.getResult(); + } catch (IOException e) { + return null; + } + } +} diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eea/EeaSendRawTransactionTransaction.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eea/EeaSendRawTransactionTransaction.java new file mode 100644 index 0000000000..2b2fb568a5 --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eea/EeaSendRawTransactionTransaction.java @@ -0,0 +1,42 @@ +/* + * Copyright 2018 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eea; + +import static org.assertj.core.api.Assertions.assertThat; + +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.JsonRequestFactories; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction; + +import java.io.IOException; + +import org.web3j.protocol.core.methods.response.EthSendTransaction; + +public class EeaSendRawTransactionTransaction implements Transaction { + + private final String transactionData; + + public EeaSendRawTransactionTransaction(final String transactionData) { + this.transactionData = transactionData; + } + + @Override + public String execute(final JsonRequestFactories node) { + try { + EthSendTransaction response = node.eea().eeaSendRawTransaction(transactionData).send(); + assertThat(response.getTransactionHash()).isNotNull(); + return response.getTransactionHash(); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eth/EthGetTransactionReceiptTransaction.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eth/EthGetTransactionReceiptTransaction.java index 58e5eecaa2..3510e63c91 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eth/EthGetTransactionReceiptTransaction.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eth/EthGetTransactionReceiptTransaction.java @@ -28,7 +28,7 @@ public class EthGetTransactionReceiptTransaction private final String input; - EthGetTransactionReceiptTransaction(final String input) { + public EthGetTransactionReceiptTransaction(final String input) { this.input = input; } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/DeployTransactionAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/DeployTransactionAcceptanceTest.java index 67b62a40f2..6b67fcd101 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/DeployTransactionAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/DeployTransactionAcceptanceTest.java @@ -36,7 +36,7 @@ public class DeployTransactionAcceptanceTest extends AcceptanceTestBase { public void transactionMustHaveReceipt() { final Hash transactionHash = minerNode.execute(transactions.createTransfer(recipient, 5)); cluster.verify(recipient.balanceEquals(5)); - minerNode.verify(eth.expectSuccessfulTransactionReceipt(transactionHash)); + minerNode.verify(eth.expectSuccessfulTransactionReceipt(transactionHash.toString())); } @Test diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/DeployPrivateSmartContractAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/DeployPrivateSmartContractAcceptanceTest.java new file mode 100644 index 0000000000..6c7b60f879 --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/DeployPrivateSmartContractAcceptanceTest.java @@ -0,0 +1,183 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.tests.web3j.privacy; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertEquals; +import static tech.pegasys.pantheon.tests.acceptance.dsl.WaitUtils.waitFor; + +import tech.pegasys.orion.testutil.OrionTestHarness; +import tech.pegasys.pantheon.crypto.SECP256K1; +import tech.pegasys.pantheon.ethereum.core.Address; +import tech.pegasys.pantheon.ethereum.core.PrivacyParameters; +import tech.pegasys.pantheon.ethereum.core.Wei; +import tech.pegasys.pantheon.ethereum.privacy.PrivateTransaction; +import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPOutput; +import tech.pegasys.pantheon.tests.acceptance.dsl.AcceptanceTestBase; +import tech.pegasys.pantheon.tests.acceptance.dsl.node.PantheonNode; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.ResponseTypes.PrivateTransactionReceipt; +import tech.pegasys.pantheon.util.bytes.BytesValue; + +import java.math.BigInteger; + +import com.google.common.collect.Lists; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.web3j.crypto.Credentials; +import org.web3j.protocol.core.methods.response.TransactionReceipt; + +public class DeployPrivateSmartContractAcceptanceTest extends AcceptanceTestBase { + + @ClassRule public static final TemporaryFolder folder = new TemporaryFolder(); + + // Contract address is generated from sender address and transaction nonce + private static final Address CONTRACT_ADDRESS = + Address.fromHexString("0x42699a7612a82f1d9c36148af9c77354759b210b"); + + private static final Address SENDER = + Address.fromHexString( + Credentials.create("8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63") + .getAddress()); + + private static final PrivateTransaction DEPLOY_CONTRACT = + PrivateTransaction.builder() + .nonce(0) + .gasPrice(Wei.of(1000)) + .gasLimit(3000000) + .to(null) + .value(Wei.ZERO) + .payload( + BytesValue.fromHexString( + "0x608060405234801561001057600080fd5b5060d08061001f60003960" + + "00f3fe60806040526004361060485763ffffffff7c01000000" + + "00000000000000000000000000000000000000000000000000" + + "60003504166360fe47b18114604d5780636d4ce63c14607557" + + "5b600080fd5b348015605857600080fd5b5060736004803603" + + "6020811015606d57600080fd5b50356099565b005b34801560" + + "8057600080fd5b506087609e565b6040805191825251908190" + + "0360200190f35b600055565b6000549056fea165627a7a7230" + + "5820cb1d0935d14b589300b12fcd0ab849a7e9019c81da24d6" + + "daa4f6b2f003d1b0180029")) + .sender(SENDER) + .chainId(2018) + .privateFrom( + BytesValue.wrap("A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=".getBytes(UTF_8))) + .privateFor( + Lists.newArrayList( + BytesValue.wrap("Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs=".getBytes(UTF_8)))) + .restriction(BytesValue.wrap("unrestricted".getBytes(UTF_8))) + .signAndBuild( + SECP256K1.KeyPair.create( + SECP256K1.PrivateKey.create( + new BigInteger( + "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63", + 16)))); + + private static final PrivateTransaction FUNCTION_CALL = + PrivateTransaction.builder() + .nonce(1) + .gasPrice(Wei.of(1000)) + .gasLimit(3000000) + .to(CONTRACT_ADDRESS) + .value(Wei.ZERO) + .payload( + BytesValue.fromHexString( + "0xcccdda2cf2895862749f1c69aa9f55cf481ea82500e4eabb4e2578b36636979b" + + "0000000000000000000000000000000000000000000000000000000000000000")) + .sender(SENDER) + .chainId(2018) + .privateFrom( + BytesValue.wrap("A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=".getBytes(UTF_8))) + .privateFor( + Lists.newArrayList( + BytesValue.wrap("Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs=".getBytes(UTF_8)))) + .restriction(BytesValue.wrap("unrestricted".getBytes(UTF_8))) + .signAndBuild( + SECP256K1.KeyPair.create( + SECP256K1.PrivateKey.create( + new BigInteger( + "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63", + 16)))); + + private PantheonNode minerNode; + + private OrionTestHarness testHarness; + + @Before + public void setUp() throws Exception { + testHarness = OrionTestHarness.create(folder.newFolder().toPath()); + + final PrivacyParameters privacyParameters = new PrivacyParameters(); + privacyParameters.setUrl(testHarness.clientUrl()); + privacyParameters.setPrivacyAddress(Address.PRIVACY); + privacyParameters.setPublicKeyUsingFile(testHarness.getConfig().publicKeys().get(0).toFile()); + privacyParameters.enablePrivateDB(folder.newFolder("private").toPath()); + + minerNode = pantheon.createPrivateTransactionEnabledMinerNode("miner-node", privacyParameters); + cluster.start(minerNode); + } + + @After + public void tearDownOnce() { + testHarness.getOrion().stop(); + } + + @Test + public void deployingMustGiveValidReceipt() { + + final String signedRawDeployTransaction = toRlp(DEPLOY_CONTRACT); + + final String transactionHash = + minerNode.execute(transactions.createPrivateRawTransaction(signedRawDeployTransaction)); + + minerNode.waitUntil(wait.chainHeadHasProgressed(minerNode, 2)); + + waitFor(() -> minerNode.verify(eth.expectSuccessfulTransactionReceipt(transactionHash))); + + TransactionReceipt txReceipt = + minerNode.execute(transactions.getTransactionReceipt(transactionHash)).get(); + + assertEquals(Address.DEFAULT_PRIVACY.toString(), txReceipt.getTo()); + + PrivateTransactionReceipt privateTxReceipt = + minerNode.execute( + transactions.getPrivateTransactionReceipt( + transactionHash, "A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=")); + + assertEquals(CONTRACT_ADDRESS.toString(), privateTxReceipt.getContractAddress()); + + final String signedRawFunctionTransaction = toRlp(FUNCTION_CALL); + + final String transactionHash1 = + minerNode.execute(transactions.createPrivateRawTransaction(signedRawFunctionTransaction)); + + minerNode.waitUntil(wait.chainHeadHasProgressed(minerNode, 2)); + + waitFor(() -> minerNode.verify(eth.expectSuccessfulTransactionReceipt(transactionHash1))); + + TransactionReceipt txReceipt2 = + minerNode.execute(transactions.getTransactionReceipt(transactionHash)).get(); + + // TODO: fire function call from minerNode and from a non-privy node + + } + + private String toRlp(final PrivateTransaction transaction) { + BytesValueRLPOutput bvrlpo = new BytesValueRLPOutput(); + transaction.writeTo(bvrlpo); + return bvrlpo.encoded().toString(); + } +} diff --git a/enclave/src/main/java/tech/pegasys/pantheon/enclave/Enclave.java b/enclave/src/main/java/tech/pegasys/pantheon/enclave/Enclave.java index 4bafaede30..3fa38a64fb 100644 --- a/enclave/src/main/java/tech/pegasys/pantheon/enclave/Enclave.java +++ b/enclave/src/main/java/tech/pegasys/pantheon/enclave/Enclave.java @@ -33,7 +33,7 @@ public class Enclave { private static final ObjectMapper objectMapper = new ObjectMapper(); private static final Logger LOG = LogManager.getLogger(); - private String url; + private final String url; private final OkHttpClient client; public Enclave(final String enclaveUrl) { diff --git a/ethereum/core/build.gradle b/ethereum/core/build.gradle index 231aec7c7a..4f524c29ff 100644 --- a/ethereum/core/build.gradle +++ b/ethereum/core/build.gradle @@ -28,11 +28,11 @@ jar { dependencies { implementation project(':config') implementation project(':crypto') + implementation project(':enclave') implementation project(':ethereum:rlp') implementation project(':ethereum:trie') implementation project(':ethereum:permissioning') implementation project(':metrics') - implementation project(':enclave') implementation project(':services:kvstore') implementation 'com.fasterxml.jackson.core:jackson-databind' diff --git a/ethereum/core/src/integration-test/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractIntegrationTest.java b/ethereum/core/src/integration-test/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractIntegrationTest.java index 51b3b642dd..88ccc71757 100644 --- a/ethereum/core/src/integration-test/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractIntegrationTest.java +++ b/ethereum/core/src/integration-test/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractIntegrationTest.java @@ -18,7 +18,6 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static org.mockito.Mockito.withSettings; import tech.pegasys.orion.testutil.OrionTestHarness; import tech.pegasys.pantheon.enclave.Enclave; @@ -26,6 +25,7 @@ import tech.pegasys.pantheon.enclave.types.SendRequest; import tech.pegasys.pantheon.enclave.types.SendResponse; import tech.pegasys.pantheon.ethereum.chain.Blockchain; import tech.pegasys.pantheon.ethereum.core.Address; +import tech.pegasys.pantheon.ethereum.core.MutableWorldState; import tech.pegasys.pantheon.ethereum.core.ProcessableBlockHeader; import tech.pegasys.pantheon.ethereum.core.WorldUpdater; import tech.pegasys.pantheon.ethereum.mainnet.SpuriousDragonGasCalculator; @@ -33,6 +33,8 @@ import tech.pegasys.pantheon.ethereum.privacy.PrivateTransaction; import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionProcessor; import tech.pegasys.pantheon.ethereum.vm.BlockHashLookup; import tech.pegasys.pantheon.ethereum.vm.MessageFrame; +import tech.pegasys.pantheon.ethereum.vm.OperationTracer; +import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; import tech.pegasys.pantheon.util.bytes.BytesValue; import java.io.IOException; @@ -72,20 +74,22 @@ public class PrivacyPrecompiledContractIntegrationTest { private static MessageFrame messageFrame; private static OrionTestHarness testHarness; + private static WorldStateArchive worldStateArchive; private PrivateTransactionProcessor mockPrivateTxProcessor() { PrivateTransactionProcessor mockPrivateTransactionProcessor = - mock(PrivateTransactionProcessor.class, withSettings().verboseLogging()); + mock(PrivateTransactionProcessor.class); PrivateTransactionProcessor.Result result = PrivateTransactionProcessor.Result.successful( null, 0, BytesValue.fromHexString(DEFAULT_OUTPUT), null); - when(mockPrivateTransactionProcessor.processPrivateTransaction( + when(mockPrivateTransactionProcessor.processTransaction( nullable(Blockchain.class), nullable(WorldUpdater.class), nullable(WorldUpdater.class), nullable(ProcessableBlockHeader.class), nullable(PrivateTransaction.class), nullable(Address.class), + nullable(OperationTracer.class), nullable(BlockHashLookup.class))) .thenReturn(result); @@ -100,6 +104,11 @@ public class PrivacyPrecompiledContractIntegrationTest { enclave = new Enclave(testHarness.clientUrl()); messageFrame = mock(MessageFrame.class); + + worldStateArchive = mock(WorldStateArchive.class); + MutableWorldState mutableWorldState = mock(MutableWorldState.class); + when(mutableWorldState.updater()).thenReturn(mock(WorldUpdater.class)); + when(worldStateArchive.getMutable()).thenReturn(mutableWorldState); } @AfterClass @@ -122,7 +131,7 @@ public class PrivacyPrecompiledContractIntegrationTest { PrivacyPrecompiledContract privacyPrecompiledContract = new PrivacyPrecompiledContract( - new SpuriousDragonGasCalculator(), publicKeys.get(0), enclave); + new SpuriousDragonGasCalculator(), publicKeys.get(0), enclave, worldStateArchive); privacyPrecompiledContract.setPrivateTransactionProcessor(mockPrivateTxProcessor()); diff --git a/ethereum/core/src/jmh/java/tech/pegasys/pantheon/ethereum/vm/operations/OperationBenchmarkHelper.java b/ethereum/core/src/jmh/java/tech/pegasys/pantheon/ethereum/vm/operations/OperationBenchmarkHelper.java index b31f53640a..194a4f7869 100644 --- a/ethereum/core/src/jmh/java/tech/pegasys/pantheon/ethereum/vm/operations/OperationBenchmarkHelper.java +++ b/ethereum/core/src/jmh/java/tech/pegasys/pantheon/ethereum/vm/operations/OperationBenchmarkHelper.java @@ -91,6 +91,7 @@ public class OperationBenchmarkHelper { .messageFrameStack(messageFrame.getMessageFrameStack()) .blockchain(messageFrame.getBlockchain()) .worldState(messageFrame.getWorldState()) + .privateWorldStates(messageFrame.getPrivateWorldStates()) .initialGas(messageFrame.getRemainingGas()) .address(messageFrame.getContractAddress()) .originator(messageFrame.getOriginatorAddress()) diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/PrivacyParameters.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/PrivacyParameters.java index 07590bcb75..3752c21a7b 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/PrivacyParameters.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/PrivacyParameters.java @@ -14,26 +14,41 @@ package tech.pegasys.pantheon.ethereum.core; import static java.nio.charset.StandardCharsets.UTF_8; +import tech.pegasys.pantheon.ethereum.storage.StorageProvider; +import tech.pegasys.pantheon.ethereum.storage.keyvalue.RocksDbStorageProvider; +import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; +import tech.pegasys.pantheon.ethereum.worldstate.WorldStateStorage; +import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; + import java.io.File; import java.io.IOException; import java.net.URI; +import java.nio.file.Path; import com.google.common.io.Files; public class PrivacyParameters { private static final String ENCLAVE_URL = "http://localhost:8888"; public static final URI DEFAULT_ENCLAVE_URL = URI.create(ENCLAVE_URL); + private final String PRIVATE_DATABASE_PATH = "private"; private Integer privacyAddress; private boolean enabled; private String url; private String publicKey; + private File publicKeyFile; + private WorldStateArchive privateWorldStateArchive; public String getPublicKey() { return publicKey; } + public File getPublicKeyFile() { + return publicKeyFile; + } + public void setPublicKeyUsingFile(final File publicKeyFile) throws IOException { + this.publicKeyFile = publicKeyFile; this.publicKey = Files.asCharSource(publicKeyFile, UTF_8).read(); } @@ -73,4 +88,17 @@ public class PrivacyParameters { public void setPrivacyAddress(final Integer privacyAddress) { this.privacyAddress = privacyAddress; } + + public void enablePrivateDB(final Path path) throws IOException { + final Path privateDbPath = path.resolve(PRIVATE_DATABASE_PATH); + final StorageProvider privateStorageProvider = + RocksDbStorageProvider.create(privateDbPath, new NoOpMetricsSystem()); + final WorldStateStorage privateWorldStateStorage = + privateStorageProvider.createWorldStateStorage(); + this.privateWorldStateArchive = new WorldStateArchive(privateWorldStateStorage); + } + + public WorldStateArchive getPrivateWorldStateArchive() { + return privateWorldStateArchive; + } } diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/ProtocolSpecBuilder.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/ProtocolSpecBuilder.java index 6d38853811..4a956ad3a0 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/ProtocolSpecBuilder.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/ProtocolSpecBuilder.java @@ -236,7 +236,7 @@ public class ProtocolSpecBuilder { checkNotNull(precompileContractRegistryBuilder, "Missing precompile contract registry"); checkNotNull(messageCallProcessorBuilder, "Missing message call processor"); checkNotNull(transactionProcessorBuilder, "Missing transaction processor"); - checkNotNull(privateTransactionProcessorBuilder, "Missing transaction processor"); + checkNotNull(privateTransactionProcessorBuilder, "Missing private transaction processor"); checkNotNull(blockHeaderValidatorBuilder, "Missing block header validator"); checkNotNull(blockBodyValidatorBuilder, "Missing block body validator"); checkNotNull(blockProcessorBuilder, "Missing block processor"); diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/TransactionValidator.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/TransactionValidator.java index 4c0ca7ce9f..4b7e16db1c 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/TransactionValidator.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/TransactionValidator.java @@ -36,7 +36,7 @@ public interface TransactionValidator { *

Note: {@code validate} should be called before getting the sender {@link Account} used in * this method to ensure that a sender can be extracted from the {@link Transaction}. * - * @param transaction the transaction to validate + * @param transaction the transaction to validateMessageFrame.State.COMPLETED_FAILED * @param sender the sender account state to validate against * @param maximumNonce the maximum transaction nonce. If not provided the transaction nonce must * equal the sender's current account nonce @@ -57,6 +57,7 @@ public interface TransactionValidator { INTRINSIC_GAS_EXCEEDS_GAS_LIMIT, EXCEEDS_BLOCK_GAS_LIMIT, TX_SENDER_NOT_AUTHORIZED, - CHAIN_HEAD_WORLD_STATE_NOT_AVAILABLE + CHAIN_HEAD_WORLD_STATE_NOT_AVAILABLE, + PRIVATE_TRANSACTION_FAILED } } diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java index d78882231e..e97115ef87 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java @@ -27,6 +27,8 @@ import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionProcessor; import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPInput; import tech.pegasys.pantheon.ethereum.vm.GasCalculator; import tech.pegasys.pantheon.ethereum.vm.MessageFrame; +import tech.pegasys.pantheon.ethereum.vm.OperationTracer; +import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; import tech.pegasys.pantheon.util.bytes.BytesValue; import java.io.IOException; @@ -38,21 +40,29 @@ import org.apache.logging.log4j.Logger; public class PrivacyPrecompiledContract extends AbstractPrecompiledContract { private final Enclave enclave; private final String enclavePublicKey; + private final WorldStateArchive privateWorldStateArchive; private PrivateTransactionProcessor privateTransactionProcessor; - private Integer DEFAULT_PRIVACY_GROUP_ID = 0; private static final Logger LOG = LogManager.getLogger(); public PrivacyPrecompiledContract( final GasCalculator gasCalculator, final PrivacyParameters privacyParameters) { - this(gasCalculator, privacyParameters.getPublicKey(), new Enclave(privacyParameters.getUrl())); + this( + gasCalculator, + privacyParameters.getPublicKey(), + new Enclave(privacyParameters.getUrl()), + privacyParameters.getPrivateWorldStateArchive()); } PrivacyPrecompiledContract( - final GasCalculator gasCalculator, final String publicKey, final Enclave enclave) { + final GasCalculator gasCalculator, + final String publicKey, + final Enclave enclave, + final WorldStateArchive worldStateArchive) { super("Privacy", gasCalculator); this.enclave = enclave; this.enclavePublicKey = publicKey; + this.privateWorldStateArchive = worldStateArchive; } public void setPrivateTransactionProcessor( @@ -76,18 +86,19 @@ public class PrivacyPrecompiledContract extends AbstractPrecompiledContract { new BytesValueRLPInput( BytesValue.wrap(Base64.getDecoder().decode(receiveResponse.getPayload())), false); - PrivateTransaction transaction = PrivateTransaction.readFrom(bytesValueRLPInput); + PrivateTransaction privateTransaction = PrivateTransaction.readFrom(bytesValueRLPInput); - WorldUpdater privateWorldState = messageFrame.getPrivateWorldState(DEFAULT_PRIVACY_GROUP_ID); WorldUpdater publicWorldState = messageFrame.getWorldState(); + WorldUpdater privateWorldState = privateWorldStateArchive.getMutable().updater(); TransactionProcessor.Result result = - privateTransactionProcessor.processPrivateTransaction( + privateTransactionProcessor.processTransaction( messageFrame.getBlockchain(), - privateWorldState, publicWorldState, + privateWorldState, messageFrame.getBlockHeader(), - transaction, + privateTransaction, messageFrame.getMiningBeneficiary(), + OperationTracer.NO_TRACING, messageFrame.getBlockHashLookup()); return result.getOutput(); diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionHandler.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionHandler.java index fb75356fcf..f1816b47c8 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionHandler.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionHandler.java @@ -66,7 +66,6 @@ public class PrivateTransactionHandler { final BytesValueRLPOutput bvrlp = new BytesValueRLPOutput(); privateTransaction.writeTo(bvrlp); - // String s = new String(bvrlp.encoded().extractArray(), UTF_8); return new SendRequest( Base64.getEncoder().encodeToString(bvrlp.encoded().extractArray()), BytesValues.asString(privateTransaction.getPrivateFrom()), diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionProcessor.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionProcessor.java index 5b913fab80..88525397d4 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionProcessor.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionProcessor.java @@ -12,8 +12,6 @@ */ package tech.pegasys.pantheon.ethereum.privacy; -import static tech.pegasys.pantheon.ethereum.vm.OperationTracer.NO_TRACING; - import tech.pegasys.pantheon.ethereum.chain.Blockchain; import tech.pegasys.pantheon.ethereum.core.Account; import tech.pegasys.pantheon.ethereum.core.Address; @@ -38,12 +36,11 @@ import tech.pegasys.pantheon.util.bytes.BytesValue; import java.util.ArrayDeque; import java.util.Deque; -import java.util.OptionalLong; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -public class PrivateTransactionProcessor implements TransactionProcessor { +public class PrivateTransactionProcessor { private static final Logger LOG = LogManager.getLogger(); @@ -55,9 +52,6 @@ public class PrivateTransactionProcessor implements TransactionProcessor { private final AbstractMessageProcessor messageCallProcessor; - private MutableAccount sender; - private Address senderAddress; - public static class Result implements TransactionProcessor.Result { private final Status status; @@ -144,96 +138,31 @@ public class PrivateTransactionProcessor implements TransactionProcessor { this.clearEmptyAccounts = clearEmptyAccounts; } - public TransactionProcessor.Result processPrivateTransaction( - final Blockchain blockchain, - final WorldUpdater privateWorldState, - final WorldUpdater publicWorldState, - final ProcessableBlockHeader blockHeader, - final PrivateTransaction privateTransaction, - final Address miningBeneficiary, - final BlockHashLookup blockHashLookup) { - Transaction transaction = - new Transaction( - privateTransaction.getNonce(), - privateTransaction.getGasPrice(), - privateTransaction.getGasLimit(), - privateTransaction.getTo(), - privateTransaction.getValue(), - privateTransaction.getSignature(), - privateTransaction.getPayload(), - privateTransaction.getSender(), - privateTransaction.getChainId().getAsInt()); - - final Address senderAddress = transaction.getSender(); - this.senderAddress = senderAddress; - Account sender = privateWorldState.get(senderAddress); - if (sender == null) { - sender = publicWorldState.get(senderAddress); - this.sender = privateWorldState.createAccount(sender.getAddress(), 0, sender.getBalance()); - } else { - this.sender = privateWorldState.getMutable(senderAddress); - } - - return processTransaction( - blockchain, - privateWorldState, - blockHeader, - transaction, - miningBeneficiary, - NO_TRACING, - blockHashLookup); - } - - @Override public Result processTransaction( final Blockchain blockchain, - final WorldUpdater worldState, + final WorldUpdater publicWorldState, + final WorldUpdater privateWorldState, final ProcessableBlockHeader blockHeader, - final Transaction transaction, + final PrivateTransaction transaction, final Address miningBeneficiary, final OperationTracer operationTracer, final BlockHashLookup blockHashLookup) { - LOG.trace("Starting execution of {}", transaction); - - ValidationResult validationResult = - transactionValidator.validate(transaction); - // Make sure the transaction is intrinsically valid before trying to - // compare against a sender account (because the transaction may not - // be signed correctly to extract the sender). - if (!validationResult.isValid()) { - LOG.warn("Invalid transaction: {}", validationResult.getErrorMessage()); - return Result.invalid(validationResult); - } + LOG.trace("Starting private execution of {}", transaction); - validationResult = - transactionValidator.validateForSender(transaction, sender, OptionalLong.empty()); - if (!validationResult.isValid()) { - LOG.warn("Invalid transaction: {}", validationResult.getErrorMessage()); - return Result.invalid(validationResult); - } + final Address senderAddress = transaction.getSender(); + final MutableAccount maybePrivateSender = privateWorldState.getMutable(senderAddress); + final MutableAccount sender = + maybePrivateSender != null + ? maybePrivateSender + : resolveAccountFromPublicState(publicWorldState, privateWorldState, senderAddress); final long previousNonce = sender.incrementNonce(); LOG.trace( - "Incremented sender {} nonce ({} -> {})", senderAddress, previousNonce, sender.getNonce()); - - final Wei upfrontGasCost = transaction.getUpfrontGasCost(); - final Wei previousBalance = sender.decrementBalance(upfrontGasCost); - LOG.trace( - "Deducted sender {} upfront gas cost {} ({} -> {})", + "Incremented private sender {} nonce ({} -> {})", senderAddress, - upfrontGasCost, - previousBalance, - sender.getBalance()); + previousNonce, + sender.getNonce()); - final Gas intrinsicGas = gasCalculator.transactionIntrinsicGasCost(transaction); - final Gas gasAvailable = Gas.of(transaction.getGasLimit()).minus(intrinsicGas); - LOG.trace( - "Gas available for execution {} = {} - {} (limit - intrinsic)", - gasAvailable, - transaction.getGasLimit(), - intrinsicGas); - - final WorldUpdater worldUpdater = worldState.updater(); final MessageFrame initialFrame; final Deque messageFrameStack = new ArrayDeque<>(); if (transaction.isContractCreation()) { @@ -245,11 +174,11 @@ public class PrivateTransactionProcessor implements TransactionProcessor { .type(MessageFrame.Type.CONTRACT_CREATION) .messageFrameStack(messageFrameStack) .blockchain(blockchain) - .worldState(worldUpdater.updater()) - .initialGas(gasAvailable) + .worldState(privateWorldState.updater()) .address(contractAddress) .originator(senderAddress) .contract(contractAddress) + .initialGas(Gas.MAX_VALUE) .gasPrice(transaction.getGasPrice()) .inputData(BytesValue.EMPTY) .sender(senderAddress) @@ -265,18 +194,18 @@ public class PrivateTransactionProcessor implements TransactionProcessor { } else { final Address to = transaction.getTo().get(); - final Account contract = worldState.get(to); + final Account contract = privateWorldState.get(to); initialFrame = MessageFrame.builder() .type(MessageFrame.Type.MESSAGE_CALL) .messageFrameStack(messageFrameStack) .blockchain(blockchain) - .worldState(worldUpdater.updater()) - .initialGas(gasAvailable) + .worldState(privateWorldState.updater()) .address(to) .originator(senderAddress) .contract(to) + .initialGas(Gas.MAX_VALUE) .gasPrice(transaction.getGasPrice()) .inputData(transaction.getPayload()) .sender(senderAddress) @@ -298,47 +227,26 @@ public class PrivateTransactionProcessor implements TransactionProcessor { } if (initialFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { - worldUpdater.commit(); - } - - if (LOG.isTraceEnabled()) { - LOG.trace( - "Gas used by transaction: {}, by message call/contract creation: {}", - () -> Gas.of(transaction.getGasLimit()).minus(initialFrame.getRemainingGas()), - () -> gasAvailable.minus(initialFrame.getRemainingGas())); - } - - // Refund the sender by what we should and pay the miner fee (note that we're doing them one - // after the other so that if it is the same account somehow, we end up with the right result) - final Gas selfDestructRefund = - gasCalculator.getSelfDestructRefundAmount().times(initialFrame.getSelfDestructs().size()); - final Gas refundGas = initialFrame.getGasRefund().plus(selfDestructRefund); - final Gas refunded = refunded(transaction, initialFrame.getRemainingGas(), refundGas); - final Wei refundedWei = refunded.priceFor(transaction.getGasPrice()); - sender.incrementBalance(refundedWei); - - final MutableAccount coinbase = worldState.getOrCreate(miningBeneficiary); - final Gas coinbaseFee = Gas.of(transaction.getGasLimit()).minus(refunded); - final Wei coinbaseWei = coinbaseFee.priceFor(transaction.getGasPrice()); - coinbase.incrementBalance(coinbaseWei); - - initialFrame.getSelfDestructs().forEach(worldState::deleteAccount); - - if (clearEmptyAccounts) { - clearEmptyAccounts(worldState); + privateWorldState.commit(); } if (initialFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { return Result.successful( - initialFrame.getLogs(), - refunded.toLong(), - initialFrame.getOutputData(), - validationResult); + initialFrame.getLogs(), 0, initialFrame.getOutputData(), ValidationResult.valid()); } else { - return Result.failed(refunded.toLong(), validationResult); + return Result.failed( + 0, ValidationResult.invalid(TransactionInvalidReason.PRIVATE_TRANSACTION_FAILED)); } } + private MutableAccount resolveAccountFromPublicState( + final WorldUpdater publicWorldState, + final WorldUpdater privateWorldState, + final Address senderAddress) { + final MutableAccount publicSender = publicWorldState.getOrCreate(senderAddress); + return privateWorldState.createAccount(senderAddress, publicSender.getNonce(), Wei.ZERO); + } + private static void clearEmptyAccounts(final WorldUpdater worldState) { worldState.getTouchedAccounts().stream() .filter(Account::isEmpty) diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/vm/MessageFrame.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/vm/MessageFrame.java index 43031ed8b8..064e070dd1 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/vm/MessageFrame.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/vm/MessageFrame.java @@ -24,9 +24,6 @@ import tech.pegasys.pantheon.ethereum.core.Transaction; import tech.pegasys.pantheon.ethereum.core.Wei; import tech.pegasys.pantheon.ethereum.core.WorldUpdater; import tech.pegasys.pantheon.ethereum.mainnet.AbstractMessageProcessor; -import tech.pegasys.pantheon.ethereum.privacy.PrivateMutableWorldState; -import tech.pegasys.pantheon.ethereum.storage.keyvalue.KeyValueStorageWorldStateStorage; -import tech.pegasys.pantheon.services.kvstore.InMemoryKeyValueStorage; import tech.pegasys.pantheon.util.bytes.Bytes32; import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.uint.UInt256; @@ -34,7 +31,6 @@ import tech.pegasys.pantheon.util.uint.UInt256Value; import java.util.Deque; import java.util.EnumSet; -import java.util.HashMap; import java.util.HashSet; import java.util.Set; import java.util.function.Consumer; @@ -185,9 +181,6 @@ public class MessageFrame { private final WorldUpdater worldState; private final Blockchain blockchain; - // Private Global data fields - private HashMap privateWorldStates; - // Metadata fields. private final Type type; private State state; @@ -652,29 +645,6 @@ public class MessageFrame { return worldState; } - /** - * Return the private world state for the corresponding privacyGroupId. - * - * @param privacyGroupId Identifier for the privacy group - * @return the private world state for that privacy group - */ - public WorldUpdater getPrivateWorldState(final Integer privacyGroupId) { - PrivateMutableWorldState privateMutableWorldState = - new PrivateMutableWorldState( - new KeyValueStorageWorldStateStorage(new InMemoryKeyValueStorage())); - return privateWorldStates.getOrDefault(privacyGroupId, privateMutableWorldState.updater()); - } - - /** - * Set the private world state for the privacyGroupId. - * - * @param privacyGroupId Identifier for the privacy group - * @param worldState Private world state for the given privacy group - */ - public void setPrivateWorldStates(final Integer privacyGroupId, final WorldUpdater worldState) { - privateWorldStates.put(privacyGroupId, worldState); - } - /** * Returns the message frame type. * diff --git a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java index 87ebffe554..db796768da 100644 --- a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java +++ b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java @@ -25,6 +25,7 @@ import tech.pegasys.pantheon.enclave.types.ReceiveRequest; import tech.pegasys.pantheon.enclave.types.ReceiveResponse; import tech.pegasys.pantheon.ethereum.chain.Blockchain; import tech.pegasys.pantheon.ethereum.core.Address; +import tech.pegasys.pantheon.ethereum.core.MutableWorldState; import tech.pegasys.pantheon.ethereum.core.ProcessableBlockHeader; import tech.pegasys.pantheon.ethereum.core.WorldUpdater; import tech.pegasys.pantheon.ethereum.mainnet.SpuriousDragonGasCalculator; @@ -32,15 +33,21 @@ import tech.pegasys.pantheon.ethereum.privacy.PrivateTransaction; import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionProcessor; import tech.pegasys.pantheon.ethereum.vm.BlockHashLookup; import tech.pegasys.pantheon.ethereum.vm.MessageFrame; +import tech.pegasys.pantheon.ethereum.vm.OperationTracer; +import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; import tech.pegasys.pantheon.util.bytes.BytesValue; import java.io.IOException; import java.util.Base64; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; public class PrivacyPrecompiledContractTest { + @Rule public final TemporaryFolder temp = new TemporaryFolder(); + private final String actual = "Test String"; private final String publicKey = "public key"; private final BytesValue key = BytesValue.wrap(actual.getBytes(UTF_8)); @@ -78,13 +85,14 @@ public class PrivacyPrecompiledContractTest { PrivateTransactionProcessor.Result result = PrivateTransactionProcessor.Result.successful( null, 0, BytesValue.fromHexString(DEFAULT_OUTPUT), null); - when(mockPrivateTransactionProcessor.processPrivateTransaction( + when(mockPrivateTransactionProcessor.processTransaction( nullable(Blockchain.class), nullable(WorldUpdater.class), nullable(WorldUpdater.class), nullable(ProcessableBlockHeader.class), nullable(PrivateTransaction.class), nullable(Address.class), + nullable(OperationTracer.class), nullable(BlockHashLookup.class))) .thenReturn(result); @@ -99,12 +107,19 @@ public class PrivacyPrecompiledContractTest { @Before public void setUp() throws IOException { + WorldStateArchive worldStateArchive; + worldStateArchive = mock(WorldStateArchive.class); + MutableWorldState mutableWorldState = mock(MutableWorldState.class); + when(mutableWorldState.updater()).thenReturn(mock(WorldUpdater.class)); + when(worldStateArchive.getMutable()).thenReturn(mutableWorldState); + privacyPrecompiledContract = - new PrivacyPrecompiledContract(new SpuriousDragonGasCalculator(), publicKey, mockEnclave()); + new PrivacyPrecompiledContract( + new SpuriousDragonGasCalculator(), publicKey, mockEnclave(), worldStateArchive); privacyPrecompiledContract.setPrivateTransactionProcessor(mockPrivateTxProcessor()); brokenPrivateTransactionHandler = new PrivacyPrecompiledContract( - new SpuriousDragonGasCalculator(), publicKey, brokenMockEnclave()); + new SpuriousDragonGasCalculator(), publicKey, brokenMockEnclave(), worldStateArchive); messageFrame = mock(MessageFrame.class); } diff --git a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionTest.java b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionTest.java index ea1493c4a2..e995f34dab 100644 --- a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionTest.java +++ b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionTest.java @@ -50,6 +50,23 @@ public class PrivateTransactionTest { + "6e766966746a69697a706a52742b4854754642733d8a726573747269637465" + "64"; + private static final String VALID_SIGNED_PRIVATE_TRANSACTION_RLP = + "0xf901a4808203e8832dc6c08080b8ef60806040523480156100105760008" + + "0fd5b5060d08061001f6000396000f3fe60806040526004361060485763f" + + "fffffff7c010000000000000000000000000000000000000000000000000" + + "000000060003504166360fe47b18114604d5780636d4ce63c146075575b6" + + "00080fd5b348015605857600080fd5b50607360048036036020811015606" + + "d57600080fd5b50356099565b005b348015608057600080fd5b506087609" + + "e565b60408051918252519081900360200190f35b600055565b600054905" + + "6fea165627a7a72305820cb1d0935d14b589300b12fcd0ab849a7e9019c8" + + "1da24d6daa4f6b2f003d1b01800292ca0a6dc7319bd355ce9d8e0928d29d" + + "9b8110bcba9168fad68498e49526420fe65dea06c4c12c2ae518c5130353" + + "eb6c2893b1c36b7fd1497c156b1e158b716f482601fac41316156744d784" + + "c4355486d425648586f5a7a7a42675062572f776a3561784470573958386" + + "c393153476f3dedac4b6f32625671442b6e4e6c4e594c354545377933496" + + "44f6e766966746a69697a706a52742b4854754642733d8a7265737472696" + + "3746564"; + private static final PrivateTransaction VALID_PRIVATE_TRANSACTION = new PrivateTransaction( 0L, @@ -75,6 +92,41 @@ public class PrivateTransactionTest { BytesValue.wrap("Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs=".getBytes(UTF_8))), BytesValue.wrap("restricted".getBytes(UTF_8))); + private static final PrivateTransaction VALID_SIGNED_PRIVATE_TRANSACTION = + PrivateTransaction.builder() + .nonce(0) + .gasPrice(Wei.of(1000)) + .gasLimit(3000000) + .to(null) + .value(Wei.ZERO) + .payload( + BytesValue.fromHexString( + "0x608060405234801561001057600080fd5b5060d08061001f6000396000" + + "f3fe60806040526004361060485763ffffffff7c010000000000" + + "0000000000000000000000000000000000000000000000600035" + + "04166360fe47b18114604d5780636d4ce63c146075575b600080" + + "fd5b348015605857600080fd5b50607360048036036020811015" + + "606d57600080fd5b50356099565b005b348015608057600080fd" + + "5b506087609e565b60408051918252519081900360200190f35b" + + "600055565b6000549056fea165627a7a72305820cb1d0935d14b" + + "589300b12fcd0ab849a7e9019c81da24d6daa4f6b2f003d1b018" + + "0029")) + .sender( + Address.wrap(BytesValue.fromHexString("0x1c9a6e1ee3b7ac6028e786d9519ae3d24ee31e79"))) + .chainId(4) + .privateFrom( + BytesValue.wrap("A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=".getBytes(UTF_8))) + .privateFor( + Lists.newArrayList( + BytesValue.wrap("Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs=".getBytes(UTF_8)))) + .restriction(BytesValue.wrap("restricted".getBytes(UTF_8))) + .signAndBuild( + SECP256K1.KeyPair.create( + SECP256K1.PrivateKey.create( + new BigInteger( + "853d7f0010fd86d0d7811c1f9d968ea89a24484a8127b4a483ddf5d2cfec766d", + 16)))); + @Test public void testWriteTo() { BytesValueRLPOutput bvrlpo = new BytesValueRLPOutput(); @@ -82,6 +134,13 @@ public class PrivateTransactionTest { assertEquals(VALID_PRIVATE_TRANSACTION_RLP, bvrlpo.encoded().toString()); } + @Test + public void testSignedWriteTo() { + BytesValueRLPOutput bvrlpo = new BytesValueRLPOutput(); + VALID_SIGNED_PRIVATE_TRANSACTION.writeTo(bvrlpo); + assertEquals(VALID_SIGNED_PRIVATE_TRANSACTION_RLP, bvrlpo.encoded().toString()); + } + @Test public void testReadFrom() { PrivateTransaction p = @@ -91,6 +150,16 @@ public class PrivateTransactionTest { assertEquals(VALID_PRIVATE_TRANSACTION, p); } + @Test + public void testSignedReadFrom() { + PrivateTransaction p = + PrivateTransaction.readFrom( + new BytesValueRLPInput( + BytesValue.fromHexString(VALID_SIGNED_PRIVATE_TRANSACTION_RLP), false)); + + assertEquals(VALID_SIGNED_PRIVATE_TRANSACTION, p); + } + @Test(expected = RLPException.class) public void testReadFromInvalid() { PrivateTransaction.readFrom( diff --git a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/worldstate/DefaultMutableWorldStateTest.java b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/worldstate/DefaultMutableWorldStateTest.java index 6251176432..51d0b6a99f 100644 --- a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/worldstate/DefaultMutableWorldStateTest.java +++ b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/worldstate/DefaultMutableWorldStateTest.java @@ -178,16 +178,15 @@ public class DefaultMutableWorldStateTest { assertEquals(newBalance, worldState.get(ADDRESS).getBalance()); // Check that storage is empty before persisting - assertEquals(0, storage.entries().count()); assertThat(kvWorldStateStorage.isWorldStateAvailable(worldState.rootHash())).isFalse(); // Persist and re-run assertions worldState.persist(); - assertNotEquals(0, storage.entries().count()); + + assertThat(kvWorldStateStorage.isWorldStateAvailable(worldState.rootHash())).isTrue(); assertEquals(expectedRootHash, worldState.rootHash()); assertNotNull(worldState.get(ADDRESS)); assertEquals(newBalance, worldState.get(ADDRESS).getBalance()); - assertThat(kvWorldStateStorage.isWorldStateAvailable(worldState.rootHash())).isTrue(); // Create new world state and check that it can access modified address final MutableWorldState newWorldState = diff --git a/ethereum/jsonrpc/build.gradle b/ethereum/jsonrpc/build.gradle index 18f0b38efb..890828a239 100644 --- a/ethereum/jsonrpc/build.gradle +++ b/ethereum/jsonrpc/build.gradle @@ -27,6 +27,7 @@ jar { dependencies { implementation project(':crypto') + implementation project(':enclave') implementation project(':ethereum:blockcreation') implementation project(':ethereum:core') implementation project(':ethereum:eth') diff --git a/ethereum/jsonrpc/src/integration-test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcTestMethodsFactory.java b/ethereum/jsonrpc/src/integration-test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcTestMethodsFactory.java index 1dbccae828..039de2b68c 100644 --- a/ethereum/jsonrpc/src/integration-test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcTestMethodsFactory.java +++ b/ethereum/jsonrpc/src/integration-test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcTestMethodsFactory.java @@ -21,6 +21,7 @@ import tech.pegasys.pantheon.ethereum.blockcreation.EthHashMiningCoordinator; import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain; import tech.pegasys.pantheon.ethereum.core.Block; import tech.pegasys.pantheon.ethereum.core.BlockImporter; +import tech.pegasys.pantheon.ethereum.core.PrivacyParameters; import tech.pegasys.pantheon.ethereum.core.Synchronizer; import tech.pegasys.pantheon.ethereum.core.TransactionPool; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.filter.FilterIdGenerator; @@ -34,7 +35,6 @@ import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSpec; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController; -import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionHandler; import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; import tech.pegasys.pantheon.metrics.MetricsSystem; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; @@ -82,8 +82,7 @@ public class JsonRpcTestMethodsFactory { final MetricsSystem metricsSystem = new NoOpMetricsSystem(); final Optional accountWhitelistController = Optional.of(mock(AccountWhitelistController.class)); - final PrivateTransactionHandler privateTransactionHandler = - mock(PrivateTransactionHandler.class); + final PrivacyParameters privacyParameters = mock(PrivacyParameters.class); return new JsonRpcMethodsFactory() .methods( @@ -99,6 +98,6 @@ public class JsonRpcTestMethodsFactory { new HashSet<>(), accountWhitelistController, RpcApis.DEFAULT_JSON_RPC_APIS, - privateTransactionHandler); + privacyParameters); } } diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java index f371c52660..44aa7fe9da 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java @@ -12,8 +12,10 @@ */ package tech.pegasys.pantheon.ethereum.jsonrpc; +import tech.pegasys.pantheon.enclave.Enclave; import tech.pegasys.pantheon.ethereum.blockcreation.MiningCoordinator; import tech.pegasys.pantheon.ethereum.chain.Blockchain; +import tech.pegasys.pantheon.ethereum.core.PrivacyParameters; import tech.pegasys.pantheon.ethereum.core.Synchronizer; import tech.pegasys.pantheon.ethereum.core.TransactionPool; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.filter.FilterManager; @@ -75,6 +77,7 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.permissioning.Per import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.permissioning.PermReloadPermissionsFromFile; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.permissioning.PermRemoveAccountsFromWhitelist; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.permissioning.PermRemoveNodesFromWhitelist; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.privacy.EeaGetTransactionReceipt; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.privacy.EeaSendRawTransaction; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.BlockReplay; @@ -115,7 +118,7 @@ public class JsonRpcMethodsFactory { final Collection rpcApis, final FilterManager filterManager, final Optional accountsWhitelistController, - final PrivateTransactionHandler privateTransactionHandler) { + final PrivacyParameters privacyParameters) { final BlockchainQueries blockchainQueries = new BlockchainQueries(blockchain, worldStateArchive); return methods( @@ -131,7 +134,7 @@ public class JsonRpcMethodsFactory { supportedCapabilities, accountsWhitelistController, rpcApis, - privateTransactionHandler); + privacyParameters); } public Map methods( @@ -147,7 +150,7 @@ public class JsonRpcMethodsFactory { final Set supportedCapabilities, final Optional accountsWhitelistController, final Collection rpcApis, - final PrivateTransactionHandler privateTransactionHandler) { + final PrivacyParameters privacyParameters) { final Map enabledMethods = new HashMap<>(); // @formatter:off if (rpcApis.contains(RpcApis.ETH)) { @@ -255,7 +258,12 @@ public class JsonRpcMethodsFactory { if (rpcApis.contains(RpcApis.EEA)) { addMethods( enabledMethods, - new EeaSendRawTransaction(privateTransactionHandler, transactionPool, parameter)); + new EeaSendRawTransaction( + new PrivateTransactionHandler(privacyParameters), transactionPool, parameter)); + addMethods( + enabledMethods, + new EeaGetTransactionReceipt( + blockchainQueries, new Enclave(privacyParameters.getUrl()), parameter)); } // @formatter:off return enabledMethods; diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionReceipt.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionReceipt.java new file mode 100644 index 0000000000..1b07cfec64 --- /dev/null +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionReceipt.java @@ -0,0 +1,93 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.privacy; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import tech.pegasys.pantheon.enclave.Enclave; +import tech.pegasys.pantheon.enclave.types.ReceiveRequest; +import tech.pegasys.pantheon.enclave.types.ReceiveResponse; +import tech.pegasys.pantheon.ethereum.core.Address; +import tech.pegasys.pantheon.ethereum.core.Hash; +import tech.pegasys.pantheon.ethereum.core.Transaction; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.JsonRpcMethod; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.BlockchainQueries; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.results.privacy.PrivateTransactionReceiptResult; +import tech.pegasys.pantheon.ethereum.privacy.PrivateTransaction; +import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPInput; +import tech.pegasys.pantheon.util.bytes.BytesValue; + +import java.io.IOException; +import java.util.Base64; +import java.util.Optional; + +public class EeaGetTransactionReceipt implements JsonRpcMethod { + + private final BlockchainQueries blockchain; + private final Enclave enclave; + private final JsonRpcParameter parameters; + + public EeaGetTransactionReceipt( + final BlockchainQueries blockchain, + final Enclave enclave, + final JsonRpcParameter parameters) { + this.blockchain = blockchain; + this.enclave = enclave; + this.parameters = parameters; + } + + @Override + public String getName() { + return "eea_getTransactionReceipt"; + } + + @Override + public JsonRpcResponse response(final JsonRpcRequest request) { + final Hash hash = parameters.required(request.getParams(), 0, Hash.class); + final String publicKey = parameters.required(request.getParams(), 1, String.class); + final Optional transactionCompleteResult = + blockchain.getBlockchain().getTransactionByHash(hash); + final PrivateTransactionReceiptResult result = + transactionCompleteResult + .map( + t -> { + final ReceiveRequest enclaveRequest = + new ReceiveRequest( + new String(t.getPayload().extractArray(), UTF_8), publicKey); + try { + ReceiveResponse enclaveResponse = enclave.receive(enclaveRequest); + final BytesValueRLPInput bytesValueRLPInput = + new BytesValueRLPInput( + BytesValue.wrap( + Base64.getDecoder().decode(enclaveResponse.getPayload())), + false); + PrivateTransaction pt = PrivateTransaction.readFrom(bytesValueRLPInput); + final String contractAddress = + Address.contractAddress(pt.getSender(), pt.getNonce()).toString(); + // TODO(PRIV): Return internal transaction emitted events and return values + return new PrivateTransactionReceiptResult( + contractAddress, + pt.getSender().toString(), + pt.getTo().map(Address::toString).orElse(null)); + } catch (IOException e) { + return null; + } + }) + .orElse(null); + return new JsonRpcSuccessResponse(request.getId(), result); + } +} diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaSendRawTransaction.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaSendRawTransaction.java index 61a0bb4a28..ce83e23151 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaSendRawTransaction.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaSendRawTransaction.java @@ -12,6 +12,7 @@ */ package tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.privacy; +import static java.nio.charset.StandardCharsets.UTF_8; import static tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcErrorConverter.convertTransactionInvalidReason; import tech.pegasys.pantheon.ethereum.core.Transaction; @@ -73,6 +74,17 @@ public class EeaSendRawTransaction implements JsonRpcMethod { return new JsonRpcErrorResponse(request.getId(), JsonRpcError.INVALID_PARAMS); } + if (!privateTransaction.getValue().isZero()) { + return new JsonRpcErrorResponse(request.getId(), JsonRpcError.INVALID_PARAMS); + } + + if (!privateTransaction + .getRestriction() + .equals(BytesValue.wrap("unrestricted".getBytes(UTF_8)))) { + return new JsonRpcErrorResponse( + request.getId(), JsonRpcError.UNIMPLEMENTED_PRIVATE_TRANSACTION_TYPE); + } + final Transaction transaction; try { transaction = handlePrivateTransaction(privateTransaction); diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java index 551662a3a5..260cd3b594 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java @@ -84,7 +84,8 @@ public enum JsonRpcError { UNAUTHORIZED(-40100, "Unauthorized"), // Private transaction errors - ENCLAVE_IS_DOWN(-32000, "Enclave is down"); + ENCLAVE_IS_DOWN(-50100, "Enclave is down"), + UNIMPLEMENTED_PRIVATE_TRANSACTION_TYPE(-50100, "Unimplemented private transaction type"); private final int code; private final String message; diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/results/privacy/PrivateTransactionReceiptResult.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/results/privacy/PrivateTransactionReceiptResult.java new file mode 100644 index 0000000000..f877998b8c --- /dev/null +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/results/privacy/PrivateTransactionReceiptResult.java @@ -0,0 +1,77 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.ethereum.jsonrpc.internal.results.privacy; + +import tech.pegasys.pantheon.ethereum.core.Hash; +import tech.pegasys.pantheon.ethereum.core.Log; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.results.TransactionReceiptLogResult; + +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonPropertyOrder({ + "contractAddress", + "from", + "to", +}) +public class PrivateTransactionReceiptResult { + + private final String contractAddress; + private final String from; + private final String to; + + public PrivateTransactionReceiptResult( + final String contractAddress, final String from, final String to) { + + this.contractAddress = contractAddress; + this.from = from; + // TODO: handle logs as in TransactionReceiptResult + this.to = to; + } + + @JsonGetter(value = "contractAddress") + public String getContractAddress() { + return contractAddress; + } + + @JsonGetter(value = "from") + public String getFrom() { + return from; + } + + @JsonGetter(value = "to") + public String getTo() { + return to; + } + + private List logReceipts( + final List logs, + final long blockNumber, + final Hash transactionHash, + final Hash blockHash, + final int transactionIndex) { + final List logResults = new ArrayList<>(logs.size()); + + for (int i = 0; i < logs.size(); i++) { + final Log log = logs.get(i); + logResults.add( + new TransactionReceiptLogResult( + log, blockNumber, transactionHash, blockHash, transactionIndex, i)); + } + + return logResults; + } +} diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/AbstractEthJsonRpcHttpServiceTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/AbstractEthJsonRpcHttpServiceTest.java index 21e7405894..ff364b577a 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/AbstractEthJsonRpcHttpServiceTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/AbstractEthJsonRpcHttpServiceTest.java @@ -27,6 +27,7 @@ import tech.pegasys.pantheon.ethereum.core.Block; import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.BlockImporter; import tech.pegasys.pantheon.ethereum.core.PendingTransactions; +import tech.pegasys.pantheon.ethereum.core.PrivacyParameters; import tech.pegasys.pantheon.ethereum.core.Synchronizer; import tech.pegasys.pantheon.ethereum.core.Transaction; import tech.pegasys.pantheon.ethereum.core.TransactionPool; @@ -44,7 +45,6 @@ import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSpec; import tech.pegasys.pantheon.ethereum.mainnet.ValidationResult; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; -import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionHandler; import tech.pegasys.pantheon.ethereum.util.RawBlockIterator; import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; @@ -148,8 +148,7 @@ public abstract class AbstractEthJsonRpcHttpServiceTest { .thenReturn(ValidationResult.valid()); final PendingTransactions pendingTransactionsMock = mock(PendingTransactions.class); when(transactionPoolMock.getPendingTransactions()).thenReturn(pendingTransactionsMock); - final PrivateTransactionHandler privateTransactionHandlerMock = - mock(PrivateTransactionHandler.class); + final PrivacyParameters privacyParameters = mock(PrivacyParameters.class); stateArchive = createInMemoryWorldStateArchive(); GENESIS_CONFIG.writeStateTo(stateArchive.getMutable()); @@ -183,7 +182,7 @@ public abstract class AbstractEthJsonRpcHttpServiceTest { supportedCapabilities, Optional.empty(), JSON_RPC_APIS, - privateTransactionHandlerMock); + privacyParameters); final JsonRpcConfiguration config = JsonRpcConfiguration.createDefault(); config.setPort(0); service = diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceHostWhitelistTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceHostWhitelistTest.java index 920268672a..3d072dd287 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceHostWhitelistTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceHostWhitelistTest.java @@ -29,7 +29,6 @@ import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController; -import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionHandler; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; import java.io.IOException; @@ -100,7 +99,7 @@ public class JsonRpcHttpServiceHostWhitelistTest { supportedCapabilities, Optional.of(mock(AccountWhitelistController.class)), JSON_RPC_APIS, - mock(PrivateTransactionHandler.class))); + mock(PrivacyParameters.class))); service = createJsonRpcHttpService(); service.start().join(); diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java index 302f5a3ba3..90622a5f1e 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java @@ -34,7 +34,6 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.BlockchainQueries import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; -import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionHandler; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; import java.io.ByteArrayInputStream; @@ -130,7 +129,7 @@ public class JsonRpcHttpServiceLoginTest { supportedCapabilities, Optional.empty(), JSON_RPC_APIS, - mock(PrivateTransactionHandler.class))); + mock(PrivacyParameters.class))); service = createJsonRpcHttpService(); jwtAuth = service.authenticationService.get().getJwtAuthProvider(); service.start().join(); diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceRpcApisTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceRpcApisTest.java index 992e9e1363..ce17d4959c 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceRpcApisTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceRpcApisTest.java @@ -18,6 +18,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import tech.pegasys.pantheon.ethereum.blockcreation.EthHashMiningCoordinator; +import tech.pegasys.pantheon.ethereum.core.PrivacyParameters; import tech.pegasys.pantheon.ethereum.core.Synchronizer; import tech.pegasys.pantheon.ethereum.core.TransactionPool; import tech.pegasys.pantheon.ethereum.eth.EthProtocol; @@ -29,7 +30,6 @@ import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController; -import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionHandler; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; import java.util.HashSet; @@ -180,7 +180,7 @@ public class JsonRpcHttpServiceRpcApisTest { supportedCapabilities, Optional.of(mock(AccountWhitelistController.class)), config.getRpcApis(), - mock(PrivateTransactionHandler.class))); + mock(PrivacyParameters.class))); final JsonRpcHttpService jsonRpcHttpService = new JsonRpcHttpService( vertx, folder.newFolder().toPath(), config, new NoOpMetricsSystem(), rpcMethods); diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceTest.java index 4c06ffb4ae..6a9e3bbacf 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceTest.java @@ -44,7 +44,6 @@ import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController; -import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionHandler; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.bytes.BytesValues; @@ -128,7 +127,7 @@ public class JsonRpcHttpServiceTest { supportedCapabilities, Optional.of(mock(AccountWhitelistController.class)), JSON_RPC_APIS, - mock(PrivateTransactionHandler.class))); + mock(PrivacyParameters.class))); service = createJsonRpcHttpService(); service.start().join(); diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionReceiptTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionReceiptTest.java new file mode 100644 index 0000000000..802ee71b61 --- /dev/null +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionReceiptTest.java @@ -0,0 +1,132 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.privacy; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import tech.pegasys.pantheon.crypto.SECP256K1; +import tech.pegasys.pantheon.enclave.Enclave; +import tech.pegasys.pantheon.enclave.types.ReceiveRequest; +import tech.pegasys.pantheon.enclave.types.ReceiveResponse; +import tech.pegasys.pantheon.ethereum.chain.Blockchain; +import tech.pegasys.pantheon.ethereum.core.Address; +import tech.pegasys.pantheon.ethereum.core.Hash; +import tech.pegasys.pantheon.ethereum.core.Transaction; +import tech.pegasys.pantheon.ethereum.core.Wei; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.BlockchainQueries; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.results.privacy.PrivateTransactionReceiptResult; +import tech.pegasys.pantheon.ethereum.privacy.PrivateTransaction; +import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPOutput; +import tech.pegasys.pantheon.util.bytes.BytesValue; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.Base64; +import java.util.Optional; + +import com.google.common.collect.Lists; +import org.junit.Test; + +public class EeaGetTransactionReceiptTest { + + private final Address sender = + Address.fromHexString("0x0000000000000000000000000000000000000003"); + private final PrivateTransaction privateTransaction = + PrivateTransaction.builder() + .nonce(0) + .gasPrice(Wei.of(1000)) + .gasLimit(3000000) + .to(null) + .value(Wei.ZERO) + .payload( + BytesValue.fromHexString( + "0x608060405234801561001057600080fd5b5060d08061001f60003960" + + "00f3fe60806040526004361060485763ffffffff7c01000000" + + "00000000000000000000000000000000000000000000000000" + + "60003504166360fe47b18114604d5780636d4ce63c14607557" + + "5b600080fd5b348015605857600080fd5b5060736004803603" + + "6020811015606d57600080fd5b50356099565b005b34801560" + + "8057600080fd5b506087609e565b6040805191825251908190" + + "0360200190f35b600055565b6000549056fea165627a7a7230" + + "5820cb1d0935d14b589300b12fcd0ab849a7e9019c81da24d6" + + "daa4f6b2f003d1b0180029")) + .sender(sender) + .chainId(2018) + .privateFrom( + BytesValue.wrap("A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=".getBytes(UTF_8))) + .privateFor( + Lists.newArrayList( + BytesValue.wrap("Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs=".getBytes(UTF_8)))) + .restriction(BytesValue.wrap("restricted".getBytes(UTF_8))) + .signAndBuild( + SECP256K1.KeyPair.create( + SECP256K1.PrivateKey.create( + new BigInteger( + "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63", + 16)))); + private final Transaction transaction = + new Transaction( + privateTransaction.getNonce(), + privateTransaction.getGasPrice(), + privateTransaction.getGasLimit(), + Optional.of(Address.DEFAULT_PRIVACY), + privateTransaction.getValue(), + privateTransaction.getSignature(), + BytesValue.wrap("EnclaveKey".getBytes(UTF_8)), + privateTransaction.getSender(), + privateTransaction.getChainId().getAsInt()); + + private final Hash hash = + Hash.fromHexString("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + private final Hash blockHash = + Hash.fromHexString("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"); + + private final JsonRpcParameter parameters = new JsonRpcParameter(); + + private final BlockchainQueries blockchainQueries = mock(BlockchainQueries.class); + private final Blockchain blockchain = mock(Blockchain.class); + private final Enclave enclave = mock(Enclave.class); + private final EeaGetTransactionReceipt eeaGetTransactionReceipt = + new EeaGetTransactionReceipt(blockchainQueries, enclave, parameters); + Object[] params = new Object[] {transaction.hash(), "EnclavePublicKey"}; + private final JsonRpcRequest request = + new JsonRpcRequest("1", "eea_getTransactionReceipt", params); + + @Test + public void createsPrivateTransactionReceipt() throws IOException { + when(blockchainQueries.getBlockchain()).thenReturn(blockchain); + when(blockchain.getTransactionByHash(transaction.hash())).thenReturn(Optional.of(transaction)); + final BytesValueRLPOutput bvrlp = new BytesValueRLPOutput(); + privateTransaction.writeTo(bvrlp); + when(enclave.receive(any(ReceiveRequest.class))) + .thenReturn( + new ReceiveResponse( + Base64.getEncoder() + .encodeToString(bvrlp.encoded().extractArray()) + .getBytes(UTF_8))); + + final JsonRpcSuccessResponse response = + (JsonRpcSuccessResponse) eeaGetTransactionReceipt.response(request); + final PrivateTransactionReceiptResult result = + (PrivateTransactionReceiptResult) response.getResult(); + + assertEquals("0x42699a7612a82f1d9c36148af9c77354759b210b", result.getContractAddress()); + } +} diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaSendRawTransactionTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaSendRawTransactionTest.java index 0f3aeebff6..febc6f53dd 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaSendRawTransactionTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaSendRawTransactionTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 ConsenSys AG. + * Copyright 2019 ConsenSys AG. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -49,16 +49,14 @@ import org.mockito.junit.MockitoJUnitRunner; public class EeaSendRawTransactionTest { private static final String VALID_PRIVATE_TRANSACTION_RLP = - "0xf90113800182520894095e7baea6a6c7c4c2dfeb977efac326af552d87" - + "a0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" - + "ffff801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d" - + "495a36649353a01fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab94" - + "9f53faa07bd2c804ac41316156744d784c4355486d425648586f5a7a7a4267" - + "5062572f776a3561784470573958386c393153476f3df85aac41316156744d" - + "784c4355486d425648586f5a7a7a42675062572f776a356178447057395838" - + "6c393153476f3dac4b6f32625671442b6e4e6c4e594c35454537793349644f" - + "6e766966746a69697a706a52742b4854754642733d8a726573747269637465" - + "64"; + "0xf8f5800182520894095e7baea6a6c7c4c2dfeb977efac326af552d87808025a04" + + "8b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a3664935" + + "3a01fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd" + + "2c804ac41316156744d784c4355486d425648586f5a7a7a42675062572f776" + + "a3561784470573958386c393153476f3df85aac41316156744d784c4355486" + + "d425648586f5a7a7a42675062572f776a3561784470573958386c393153476" + + "f3dac4b6f32625671442b6e4e6c4e594c35454537793349644f6e766966746" + + "a69697a706a52742b4854754642733d8c756e72657374726963746564"; private static final Transaction PUBLIC_TRANSACTION = new Transaction( @@ -67,9 +65,7 @@ public class EeaSendRawTransactionTest { 21000L, Optional.of( Address.wrap(BytesValue.fromHexString("0x095e7baea6a6c7c4c2dfeb977efac326af552d87"))), - Wei.of( - new BigInteger( - "115792089237316195423570985008687907853269984665640564039457584007913129639935")), + Wei.ZERO, SECP256K1.Signature.create( new BigInteger( "32886959230931919120748662916110619501838190146643992583529828535682419954515"), @@ -161,7 +157,7 @@ public class EeaSendRawTransactionTest { final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse( - request.getId(), "0xa86e8a2324e3abccd52afd6913c4c8a5d91f5d1855c0aa075568416c0a3ff7b2"); + request.getId(), "0x221e930a2c18d91fca4d509eaa3512f3e01fef266f660e32473de67474b36c15"); final JsonRpcResponse actualResponse = method.response(request); diff --git a/ethereum/trie/src/test/java/tech/pegasys/pantheon/ethereum/trie/StoredMerklePatriciaTrieTest.java b/ethereum/trie/src/test/java/tech/pegasys/pantheon/ethereum/trie/StoredMerklePatriciaTrieTest.java index 4c03473b3a..cb8932d8d8 100644 --- a/ethereum/trie/src/test/java/tech/pegasys/pantheon/ethereum/trie/StoredMerklePatriciaTrieTest.java +++ b/ethereum/trie/src/test/java/tech/pegasys/pantheon/ethereum/trie/StoredMerklePatriciaTrieTest.java @@ -349,9 +349,7 @@ public class StoredMerklePatriciaTrieTest { assertThat(trie.get(key3)).isEqualTo(Optional.of("value3")); // Commit changes to storage, and create new tries from roothash and new storage instance - assertThat(keyValueStore.entries().count()).isEqualTo(0); merkleStorage.commit(); - assertThat(keyValueStore.entries().count()).isGreaterThan(0); final MerkleStorage newMerkleStorage = new KeyValueMerkleStorage(keyValueStore); trie = new StoredMerklePatriciaTrie<>( diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/RunnerBuilder.java b/pantheon/src/main/java/tech/pegasys/pantheon/RunnerBuilder.java index c7211710f1..c20eabaff7 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/RunnerBuilder.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/RunnerBuilder.java @@ -54,7 +54,6 @@ import tech.pegasys.pantheon.ethereum.p2p.wire.SubProtocol; import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController; import tech.pegasys.pantheon.ethereum.permissioning.NodeWhitelistController; import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration; -import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionHandler; import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; import tech.pegasys.pantheon.metrics.MetricsSystem; import tech.pegasys.pantheon.metrics.prometheus.MetricsConfiguration; @@ -259,9 +258,6 @@ public class RunnerBuilder { }); final PrivacyParameters privacyParameters = pantheonController.getPrivacyParameters(); - final PrivateTransactionHandler privateTransactionHandler = - new PrivateTransactionHandler(privacyParameters); - final FilterManager filterManager = createFilterManager(vertx, context, transactionPool); Optional jsonRpcHttpService = Optional.empty(); @@ -280,7 +276,7 @@ public class RunnerBuilder { jsonRpcConfiguration.getRpcApis(), filterManager, accountWhitelistController, - privateTransactionHandler); + privacyParameters); jsonRpcHttpService = Optional.of( new JsonRpcHttpService( @@ -303,7 +299,7 @@ public class RunnerBuilder { webSocketConfiguration.getRpcApis(), filterManager, accountWhitelistController, - privateTransactionHandler); + privacyParameters); final SubscriptionManager subscriptionManager = createSubscriptionManager( @@ -363,7 +359,7 @@ public class RunnerBuilder { final Collection jsonRpcApis, final FilterManager filterManager, final Optional accountWhitelistController, - final PrivateTransactionHandler privateTransactionHandler) { + final PrivacyParameters privacyParameters) { final Map methods = new JsonRpcMethodsFactory() .methods( @@ -380,7 +376,7 @@ public class RunnerBuilder { jsonRpcApis, filterManager, accountWhitelistController, - privateTransactionHandler); + privacyParameters); methods.putAll(pantheonController.getAdditionalJsonRpcMethods(jsonRpcApis)); return methods; } diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java index c11c3ecba2..6fe3661d3c 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java @@ -446,7 +446,7 @@ public class PantheonCommand implements DefaultCommandValues, Runnable { @Option( names = {"--privacy-enabled"}, description = "Enable private transactions (default: ${DEFAULT-VALUE})") - private final Boolean privacyEnabled = false; + private final Boolean isPrivacyEnabled = false; @Option( names = {"--privacy-url"}, @@ -791,13 +791,12 @@ public class PantheonCommand implements DefaultCommandValues, Runnable { logger, commandLine, "--privacy-enabled", - !privacyEnabled, + !isPrivacyEnabled, Arrays.asList( "--privacy-url", "--privacy-public-key-file", "--privacy-precompiled-address")); final PrivacyParameters privacyParameters = PrivacyParameters.noPrivacy(); - if (privacyEnabled) { - privacyParameters.setEnabled(privacyEnabled); + if (isPrivacyEnabled) { privacyParameters.setUrl(privacyUrl.toString()); if (privacyPublicKeyFile() != null) { privacyParameters.setPublicKeyUsingFile(privacyPublicKeyFile()); @@ -806,6 +805,7 @@ public class PantheonCommand implements DefaultCommandValues, Runnable { commandLine, "Please specify Enclave public key file path to enable privacy"); } privacyParameters.setPrivacyAddress(privacyPrecompiledAddress); + privacyParameters.enablePrivateDB(dataDir()); } return privacyParameters; } diff --git a/services/kvstore/src/main/java/tech/pegasys/pantheon/services/kvstore/InMemoryKeyValueStorage.java b/services/kvstore/src/main/java/tech/pegasys/pantheon/services/kvstore/InMemoryKeyValueStorage.java index 5585364eae..bdb43df551 100644 --- a/services/kvstore/src/main/java/tech/pegasys/pantheon/services/kvstore/InMemoryKeyValueStorage.java +++ b/services/kvstore/src/main/java/tech/pegasys/pantheon/services/kvstore/InMemoryKeyValueStorage.java @@ -22,8 +22,6 @@ import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.stream.Collectors; -import java.util.stream.Stream; public class InMemoryKeyValueStorage implements KeyValueStorage { @@ -46,21 +44,6 @@ public class InMemoryKeyValueStorage implements KeyValueStorage { return new InMemoryTransaction(); } - @Override - public Stream entries() { - final Lock lock = rwLock.readLock(); - lock.lock(); - try { - // Ensure we have collected all entries before releasing the lock and returning - return hashValueStore.entrySet().stream() - .map(e -> Entry.create(e.getKey(), e.getValue())) - .collect(Collectors.toSet()) - .stream(); - } finally { - lock.unlock(); - } - } - @Override public void close() {} diff --git a/services/kvstore/src/main/java/tech/pegasys/pantheon/services/kvstore/KeyValueStorage.java b/services/kvstore/src/main/java/tech/pegasys/pantheon/services/kvstore/KeyValueStorage.java index 9e10269aea..2dbd753de2 100644 --- a/services/kvstore/src/main/java/tech/pegasys/pantheon/services/kvstore/KeyValueStorage.java +++ b/services/kvstore/src/main/java/tech/pegasys/pantheon/services/kvstore/KeyValueStorage.java @@ -19,7 +19,6 @@ import tech.pegasys.pantheon.util.bytes.BytesValue; import java.io.Closeable; import java.util.Objects; import java.util.Optional; -import java.util.stream.Stream; /** Service provided by pantheon to facilitate persistent data storage. */ public interface KeyValueStorage extends Closeable { @@ -37,13 +36,6 @@ public interface KeyValueStorage extends Closeable { */ Transaction startTransaction() throws StorageException; - /** - * Stream all stored key-value pairs. - * - * @return A stream of the contained key-value pairs. - */ - Stream entries(); - class Entry { private final BytesValue key; private final BytesValue value; diff --git a/services/kvstore/src/main/java/tech/pegasys/pantheon/services/kvstore/RocksDbKeyValueStorage.java b/services/kvstore/src/main/java/tech/pegasys/pantheon/services/kvstore/RocksDbKeyValueStorage.java index 14bdc1b02f..2909ea9085 100644 --- a/services/kvstore/src/main/java/tech/pegasys/pantheon/services/kvstore/RocksDbKeyValueStorage.java +++ b/services/kvstore/src/main/java/tech/pegasys/pantheon/services/kvstore/RocksDbKeyValueStorage.java @@ -109,14 +109,6 @@ public class RocksDbKeyValueStorage implements KeyValueStorage, Closeable { return new RocksDbTransaction(db.beginTransaction(options), options); } - @Override - public Stream entries() { - throwIfClosed(); - final RocksIterator rocksIt = db.newIterator(); - rocksIt.seekToFirst(); - return new RocksDbEntryIterator(rocksIt).toStream(); - } - @Override public void close() { if (closed.compareAndSet(false, true)) { diff --git a/services/kvstore/src/test/java/tech/pegasys/pantheon/services/kvstore/AbstractKeyValueStorageTest.java b/services/kvstore/src/test/java/tech/pegasys/pantheon/services/kvstore/AbstractKeyValueStorageTest.java index eced4071dc..4ad98f657b 100644 --- a/services/kvstore/src/test/java/tech/pegasys/pantheon/services/kvstore/AbstractKeyValueStorageTest.java +++ b/services/kvstore/src/test/java/tech/pegasys/pantheon/services/kvstore/AbstractKeyValueStorageTest.java @@ -16,18 +16,14 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import tech.pegasys.pantheon.services.kvstore.KeyValueStorage.Entry; import tech.pegasys.pantheon.services.kvstore.KeyValueStorage.Transaction; import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.bytes.BytesValues; import java.util.Arrays; -import java.util.Comparator; -import java.util.List; import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.function.Function; -import java.util.stream.Collectors; import org.junit.Ignore; import org.junit.Test; @@ -97,27 +93,6 @@ public abstract class AbstractKeyValueStorageTest { assertEquals(Optional.empty(), store.get(BytesValue.fromHexString("0F"))); } - @Test - public void entries() throws Exception { - final KeyValueStorage store = createStore(); - - final List testEntries = - Arrays.asList( - Entry.create(BytesValue.fromHexString("01"), BytesValue.fromHexString("0ABC")), - Entry.create(BytesValue.fromHexString("02"), BytesValue.fromHexString("0DEF"))); - Transaction tx = store.startTransaction(); - for (final Entry testEntry : testEntries) { - tx.put(testEntry.getKey(), testEntry.getValue()); - } - tx.commit(); - - final List actualEntries = store.entries().collect(Collectors.toList()); - testEntries.sort(Comparator.comparing(Entry::getKey)); - actualEntries.sort(Comparator.comparing(Entry::getKey)); - assertEquals(2, actualEntries.size()); - assertEquals(testEntries, actualEntries); - } - @Test public void concurrentUpdate() throws Exception { final int keyCount = 1000; diff --git a/testutil/src/main/java/tech/pegasys/orion/testutil/OrionTestHarness.java b/testutil/src/main/java/tech/pegasys/orion/testutil/OrionTestHarness.java index d1cc4ac16b..4e3bc49643 100644 --- a/testutil/src/main/java/tech/pegasys/orion/testutil/OrionTestHarness.java +++ b/testutil/src/main/java/tech/pegasys/orion/testutil/OrionTestHarness.java @@ -24,9 +24,13 @@ import com.google.common.base.Charsets; import net.consensys.orion.cmd.Orion; import net.consensys.orion.config.Config; import okhttp3.HttpUrl; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class OrionTestHarness { + private static final Logger LOG = LogManager.getLogger(); + private final Orion orion; private final Config config; @@ -76,6 +80,9 @@ public class OrionTestHarness { final Orion orion = new Orion(); orion.run(System.out, System.err, config); + LOG.info("Orion node port: {}", orion.nodePort()); + LOG.info("Orion client port: {}", orion.clientPort()); + return new OrionTestHarness(orion, config); }