[PRIV] Complete Private Transaction Processor (#938)

* Remove entries() in KeyValueStorage

* Implement Private Transaction Processor

* Implement PrivateTransactionReceipt

* Refactor privacy parameter injection

* Disable ether for private transaction

* Implement AcceptanceTest

* Fix PrivateWorldState injection

* Add unimplemented private transaction type

- https://entethalliance.github.io/client-spec/spec.html#sec-extensions-json-rpc

* Change Private Transaction RLP

* Change Private Transaction RLP for acceptanceTest

* Fix copyright year

* Fix build

Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>
pull/2/head
Ivaylo Kirilov 6 years ago committed by Lucas Saldanha
parent 6176a63049
commit 582454581a
  1. 3
      acceptance-tests/build.gradle
  2. 5
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Eth.java
  3. 14
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java
  4. 10
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ProcessPantheonNodeRunner.java
  5. 3
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ThreadPantheonNodeRunner.java
  6. 8
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonFactoryConfiguration.java
  7. 12
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonFactoryConfigurationBuilder.java
  8. 14
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonNodeFactory.java
  9. 48
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/EeaJsonRpcRequestFactory.java
  10. 7
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/JsonRequestFactories.java
  11. 35
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/ResponseTypes.java
  12. 17
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/Transactions.java
  13. 46
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eea/EeaGetTransactionReceiptTransaction.java
  14. 42
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eea/EeaSendRawTransactionTransaction.java
  15. 2
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eth/EthGetTransactionReceiptTransaction.java
  16. 2
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/DeployTransactionAcceptanceTest.java
  17. 183
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/DeployPrivateSmartContractAcceptanceTest.java
  18. 2
      enclave/src/main/java/tech/pegasys/pantheon/enclave/Enclave.java
  19. 2
      ethereum/core/build.gradle
  20. 17
      ethereum/core/src/integration-test/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractIntegrationTest.java
  21. 1
      ethereum/core/src/jmh/java/tech/pegasys/pantheon/ethereum/vm/operations/OperationBenchmarkHelper.java
  22. 28
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/PrivacyParameters.java
  23. 2
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/ProtocolSpecBuilder.java
  24. 5
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/TransactionValidator.java
  25. 27
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java
  26. 1
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionHandler.java
  27. 154
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionProcessor.java
  28. 30
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/vm/MessageFrame.java
  29. 21
      ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java
  30. 69
      ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionTest.java
  31. 5
      ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/worldstate/DefaultMutableWorldStateTest.java
  32. 1
      ethereum/jsonrpc/build.gradle
  33. 7
      ethereum/jsonrpc/src/integration-test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcTestMethodsFactory.java
  34. 16
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java
  35. 93
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionReceipt.java
  36. 12
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaSendRawTransaction.java
  37. 3
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java
  38. 77
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/results/privacy/PrivateTransactionReceiptResult.java
  39. 7
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/AbstractEthJsonRpcHttpServiceTest.java
  40. 3
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceHostWhitelistTest.java
  41. 3
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java
  42. 4
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceRpcApisTest.java
  43. 3
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceTest.java
  44. 132
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionReceiptTest.java
  45. 26
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaSendRawTransactionTest.java
  46. 2
      ethereum/trie/src/test/java/tech/pegasys/pantheon/ethereum/trie/StoredMerklePatriciaTrieTest.java
  47. 12
      pantheon/src/main/java/tech/pegasys/pantheon/RunnerBuilder.java
  48. 8
      pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java
  49. 17
      services/kvstore/src/main/java/tech/pegasys/pantheon/services/kvstore/InMemoryKeyValueStorage.java
  50. 8
      services/kvstore/src/main/java/tech/pegasys/pantheon/services/kvstore/KeyValueStorage.java
  51. 8
      services/kvstore/src/main/java/tech/pegasys/pantheon/services/kvstore/RocksDbKeyValueStorage.java
  52. 25
      services/kvstore/src/test/java/tech/pegasys/pantheon/services/kvstore/AbstractKeyValueStorageTest.java
  53. 7
      testutil/src/main/java/tech/pegasys/orion/testutil/OrionTestHarness.java

@ -25,8 +25,10 @@ dependencies {
testImplementation project(':ethereum:blockcreation') testImplementation project(':ethereum:blockcreation')
testImplementation project(':ethereum:jsonrpc') testImplementation project(':ethereum:jsonrpc')
testImplementation project(':ethereum:permissioning') testImplementation project(':ethereum:permissioning')
testImplementation project(':ethereum:rlp')
testImplementation project(':metrics') testImplementation project(':metrics')
testImplementation project(':pantheon') testImplementation project(':pantheon')
testImplementation project(':testutil')
testImplementation project(':util') testImplementation project(':util')
testImplementation project(path: ':ethereum:core', configuration: 'testSupportArtifacts') testImplementation project(path: ':ethereum:core', configuration: 'testSupportArtifacts')
@ -35,6 +37,7 @@ dependencies {
testImplementation 'io.reactivex.rxjava2:rxjava' testImplementation 'io.reactivex.rxjava2:rxjava'
testImplementation 'io.vertx:vertx-core' testImplementation 'io.vertx:vertx-core'
testImplementation 'junit:junit' testImplementation 'junit:junit'
testImplementation 'net.consensys:orion'
testImplementation 'org.apache.logging.log4j:log4j-api' testImplementation 'org.apache.logging.log4j:log4j-api'
testImplementation 'org.assertj:assertj-core' testImplementation 'org.assertj:assertj-core'
testImplementation 'org.awaitility:awaitility' testImplementation 'org.awaitility:awaitility'

@ -12,7 +12,6 @@
*/ */
package tech.pegasys.pantheon.tests.acceptance.dsl.jsonrpc; 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.Condition;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.eth.ExpectEthAccountsException; import tech.pegasys.pantheon.tests.acceptance.dsl.condition.eth.ExpectEthAccountsException;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.eth.ExpectEthGetTransactionReceiptIsAbsent; import tech.pegasys.pantheon.tests.acceptance.dsl.condition.eth.ExpectEthGetTransactionReceiptIsAbsent;
@ -42,9 +41,9 @@ public class Eth {
return new ExpectEthAccountsException(transactions.accounts(), expectedMessage); return new ExpectEthAccountsException(transactions.accounts(), expectedMessage);
} }
public Condition expectSuccessfulTransactionReceipt(final Hash transactionHash) { public Condition expectSuccessfulTransactionReceipt(final String transactionHash) {
return new ExpectSuccessfulEthGetTransactionReceipt( return new ExpectSuccessfulEthGetTransactionReceipt(
transactions.getTransactionReceipt(transactionHash.toString())); transactions.getTransactionReceipt(transactionHash));
} }
public Condition expectNoTransactionReceipt(final String transactionHash) { public Condition expectNoTransactionReceipt(final String transactionHash) {

@ -19,6 +19,7 @@ import tech.pegasys.pantheon.controller.KeyPairUtil;
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.ethereum.core.Address; import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.MiningParameters; 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.core.Util;
import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration; import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration;
import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration; 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.httptransaction.HttpTransaction;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.AdminJsonRpcRequestFactory; 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.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.IbftJsonRpcRequestFactory;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.JsonRequestFactories; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.JsonRequestFactories;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.PermissioningJsonRpcRequestFactory; 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 String name;
private final MiningParameters miningParameters; private final MiningParameters miningParameters;
private final PrivacyParameters privacyParameters;
private final JsonRpcConfiguration jsonRpcConfiguration; private final JsonRpcConfiguration jsonRpcConfiguration;
private final WebSocketConfiguration webSocketConfiguration; private final WebSocketConfiguration webSocketConfiguration;
private final MetricsConfiguration metricsConfiguration; private final MetricsConfiguration metricsConfiguration;
@ -98,6 +101,7 @@ public class PantheonNode implements Node, NodeConfiguration, RunnableNode, Auto
public PantheonNode( public PantheonNode(
final String name, final String name,
final MiningParameters miningParameters, final MiningParameters miningParameters,
final PrivacyParameters privacyParameters,
final JsonRpcConfiguration jsonRpcConfiguration, final JsonRpcConfiguration jsonRpcConfiguration,
final WebSocketConfiguration webSocketConfiguration, final WebSocketConfiguration webSocketConfiguration,
final MetricsConfiguration metricsConfiguration, final MetricsConfiguration metricsConfiguration,
@ -114,6 +118,7 @@ public class PantheonNode implements Node, NodeConfiguration, RunnableNode, Auto
this.p2pPort = p2pPort; this.p2pPort = p2pPort;
this.name = name; this.name = name;
this.miningParameters = miningParameters; this.miningParameters = miningParameters;
this.privacyParameters = privacyParameters;
this.jsonRpcConfiguration = jsonRpcConfiguration; this.jsonRpcConfiguration = jsonRpcConfiguration;
this.webSocketConfiguration = webSocketConfiguration; this.webSocketConfiguration = webSocketConfiguration;
this.metricsConfiguration = metricsConfiguration; this.metricsConfiguration = metricsConfiguration;
@ -234,6 +239,7 @@ public class PantheonNode implements Node, NodeConfiguration, RunnableNode, Auto
new IbftJsonRpcRequestFactory(web3jService), new IbftJsonRpcRequestFactory(web3jService),
new PermissioningJsonRpcRequestFactory(web3jService), new PermissioningJsonRpcRequestFactory(web3jService),
new AdminJsonRpcRequestFactory(web3jService), new AdminJsonRpcRequestFactory(web3jService),
new EeaJsonRpcRequestFactory(web3jService),
websocketService); websocketService);
} }
@ -292,6 +298,10 @@ public class PantheonNode implements Node, NodeConfiguration, RunnableNode, Auto
this.token = token; this.token = token;
} }
public KeyPair getKeyPair() {
return keyPair;
}
private void checkIfWebSocketEndpointIsAvailable(final String url) { private void checkIfWebSocketEndpointIsAvailable(final String url) {
final WebSocketClient webSocketClient = new WebSocketClient(URI.create(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 // 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; return miningParameters;
} }
public PrivacyParameters getPrivacyParameters() {
return privacyParameters;
}
public boolean isDevMode() { public boolean isDevMode() {
return devMode; return devMode;
} }

@ -71,6 +71,16 @@ public class ProcessPantheonNodeRunner implements PantheonNodeRunner {
params.add(node.getMiningParameters().getCoinbase().get().toString()); 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("--bootnodes");
params.add(String.join(",", node.bootnodes().toString())); params.add(String.join(",", node.bootnodes().toString()));

@ -20,7 +20,6 @@ import tech.pegasys.pantheon.cli.EthNetworkConfig;
import tech.pegasys.pantheon.cli.PantheonControllerBuilder; import tech.pegasys.pantheon.cli.PantheonControllerBuilder;
import tech.pegasys.pantheon.controller.KeyPairUtil; import tech.pegasys.pantheon.controller.KeyPairUtil;
import tech.pegasys.pantheon.controller.PantheonController; 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.ethereum.eth.sync.SynchronizerConfiguration;
import tech.pegasys.pantheon.metrics.MetricsSystem; import tech.pegasys.pantheon.metrics.MetricsSystem;
import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem;
@ -69,10 +68,10 @@ public class ThreadPantheonNodeRunner implements PantheonNodeRunner {
.ethNetworkConfig(ethNetworkConfig) .ethNetworkConfig(ethNetworkConfig)
.syncWithOttoman(false) .syncWithOttoman(false)
.miningParameters(node.getMiningParameters()) .miningParameters(node.getMiningParameters())
.privacyParameters(node.getPrivacyParameters())
.devMode(node.isDevMode()) .devMode(node.isDevMode())
.nodePrivateKeyFile(KeyPairUtil.getDefaultKeyFile(node.homeDirectory())) .nodePrivateKeyFile(KeyPairUtil.getDefaultKeyFile(node.homeDirectory()))
.metricsSystem(noOpMetricsSystem) .metricsSystem(noOpMetricsSystem)
.privacyParameters(PrivacyParameters.noPrivacy())
.build(); .build();
} catch (final IOException e) { } catch (final IOException e) {
throw new RuntimeException("Error building PantheonController", e); throw new RuntimeException("Error building PantheonController", e);

@ -13,6 +13,7 @@
package tech.pegasys.pantheon.tests.acceptance.dsl.node.factory; package tech.pegasys.pantheon.tests.acceptance.dsl.node.factory;
import tech.pegasys.pantheon.ethereum.core.MiningParameters; 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.JsonRpcConfiguration;
import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration; import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration;
import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration; import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration;
@ -25,6 +26,7 @@ class PantheonFactoryConfiguration {
private final String name; private final String name;
private final MiningParameters miningParameters; private final MiningParameters miningParameters;
private final PrivacyParameters privacyParameters;
private final JsonRpcConfiguration jsonRpcConfiguration; private final JsonRpcConfiguration jsonRpcConfiguration;
private final WebSocketConfiguration webSocketConfiguration; private final WebSocketConfiguration webSocketConfiguration;
private final MetricsConfiguration metricsConfiguration; private final MetricsConfiguration metricsConfiguration;
@ -38,6 +40,7 @@ class PantheonFactoryConfiguration {
PantheonFactoryConfiguration( PantheonFactoryConfiguration(
final String name, final String name,
final MiningParameters miningParameters, final MiningParameters miningParameters,
final PrivacyParameters privacyParameters,
final JsonRpcConfiguration jsonRpcConfiguration, final JsonRpcConfiguration jsonRpcConfiguration,
final WebSocketConfiguration webSocketConfiguration, final WebSocketConfiguration webSocketConfiguration,
final MetricsConfiguration metricsConfiguration, final MetricsConfiguration metricsConfiguration,
@ -49,6 +52,7 @@ class PantheonFactoryConfiguration {
final boolean isBootnode) { final boolean isBootnode) {
this.name = name; this.name = name;
this.miningParameters = miningParameters; this.miningParameters = miningParameters;
this.privacyParameters = privacyParameters;
this.jsonRpcConfiguration = jsonRpcConfiguration; this.jsonRpcConfiguration = jsonRpcConfiguration;
this.webSocketConfiguration = webSocketConfiguration; this.webSocketConfiguration = webSocketConfiguration;
this.metricsConfiguration = metricsConfiguration; this.metricsConfiguration = metricsConfiguration;
@ -68,6 +72,10 @@ class PantheonFactoryConfiguration {
return miningParameters; return miningParameters;
} }
public PrivacyParameters getPrivacyParameters() {
return privacyParameters;
}
public JsonRpcConfiguration getJsonRpcConfiguration() { public JsonRpcConfiguration getJsonRpcConfiguration() {
return jsonRpcConfiguration; return jsonRpcConfiguration;
} }

@ -16,7 +16,9 @@ import static java.util.Collections.singletonList;
import tech.pegasys.pantheon.ethereum.core.MiningParameters; import tech.pegasys.pantheon.ethereum.core.MiningParameters;
import tech.pegasys.pantheon.ethereum.core.MiningParametersTestBuilder; 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.JsonRpcConfiguration;
import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApis;
import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration; import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration;
import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration; import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration;
import tech.pegasys.pantheon.metrics.prometheus.MetricsConfiguration; import tech.pegasys.pantheon.metrics.prometheus.MetricsConfiguration;
@ -32,6 +34,7 @@ public class PantheonFactoryConfigurationBuilder {
private String name; private String name;
private MiningParameters miningParameters = private MiningParameters miningParameters =
new MiningParametersTestBuilder().enabled(false).build(); new MiningParametersTestBuilder().enabled(false).build();
private PrivacyParameters privacyParameters = PrivacyParameters.noPrivacy();
private JsonRpcConfiguration jsonRpcConfiguration = JsonRpcConfiguration.createDefault(); private JsonRpcConfiguration jsonRpcConfiguration = JsonRpcConfiguration.createDefault();
private WebSocketConfiguration webSocketConfiguration = WebSocketConfiguration.createDefault(); private WebSocketConfiguration webSocketConfiguration = WebSocketConfiguration.createDefault();
private MetricsConfiguration metricsConfiguration = MetricsConfiguration.createDefault(); private MetricsConfiguration metricsConfiguration = MetricsConfiguration.createDefault();
@ -72,6 +75,14 @@ public class PantheonFactoryConfigurationBuilder {
return this; 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() public PantheonFactoryConfigurationBuilder jsonRpcAuthenticationEnabled()
throws URISyntaxException { throws URISyntaxException {
final String authTomlPath = final String authTomlPath =
@ -156,6 +167,7 @@ public class PantheonFactoryConfigurationBuilder {
return new PantheonFactoryConfiguration( return new PantheonFactoryConfiguration(
name, name,
miningParameters, miningParameters,
privacyParameters,
jsonRpcConfiguration, jsonRpcConfiguration,
webSocketConfiguration, webSocketConfiguration,
metricsConfiguration, metricsConfiguration,

@ -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.clique.CliqueExtraData;
import tech.pegasys.pantheon.consensus.ibft.IbftExtraData; import tech.pegasys.pantheon.consensus.ibft.IbftExtraData;
import tech.pegasys.pantheon.ethereum.core.Address; 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.JsonRpcConfiguration;
import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApi; import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApi;
import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApis; import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApis;
@ -56,6 +57,7 @@ public class PantheonNodeFactory {
new PantheonNode( new PantheonNode(
config.getName(), config.getName(),
config.getMiningParameters(), config.getMiningParameters(),
config.getPrivacyParameters(),
config.getJsonRpcConfiguration(), config.getJsonRpcConfiguration(),
config.getWebSocketConfiguration(), config.getWebSocketConfiguration(),
config.getMetricsConfiguration(), config.getMetricsConfiguration(),
@ -81,6 +83,18 @@ public class PantheonNodeFactory {
.build()); .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( public PantheonNode createMinerNodeWithCustomRefreshDelay(
final String name, final Long refreshDelay) throws IOException { final String name, final Long refreshDelay) throws IOException {

@ -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<?, org.web3j.protocol.core.methods.response.EthSendTransaction>
eeaSendRawTransaction(final String signedTransactionData) {
return new Request<>(
"eea_sendRawTransaction",
Collections.singletonList(signedTransactionData),
web3jService,
org.web3j.protocol.core.methods.response.EthSendTransaction.class);
}
public Request<?, PrivateTransactionReceiptResponse> eeaGetTransactionReceipt(
final String txHash, final String publicKey) {
return new Request<>(
"eea_getTransactionReceipt",
Lists.newArrayList(txHash, publicKey),
web3jService,
PrivateTransactionReceiptResponse.class);
}
}

@ -24,6 +24,7 @@ public class JsonRequestFactories {
private final IbftJsonRpcRequestFactory ibft; private final IbftJsonRpcRequestFactory ibft;
private final PermissioningJsonRpcRequestFactory perm; private final PermissioningJsonRpcRequestFactory perm;
private final AdminJsonRpcRequestFactory admin; private final AdminJsonRpcRequestFactory admin;
private final EeaJsonRpcRequestFactory eea;
private final Optional<WebSocketService> websocketService; private final Optional<WebSocketService> websocketService;
public JsonRequestFactories( public JsonRequestFactories(
@ -32,12 +33,14 @@ public class JsonRequestFactories {
final IbftJsonRpcRequestFactory ibft, final IbftJsonRpcRequestFactory ibft,
final PermissioningJsonRpcRequestFactory perm, final PermissioningJsonRpcRequestFactory perm,
final AdminJsonRpcRequestFactory admin, final AdminJsonRpcRequestFactory admin,
final EeaJsonRpcRequestFactory eea,
final Optional<WebSocketService> websocketService) { final Optional<WebSocketService> websocketService) {
this.netEth = netEth; this.netEth = netEth;
this.clique = clique; this.clique = clique;
this.ibft = ibft; this.ibft = ibft;
this.perm = perm; this.perm = perm;
this.admin = admin; this.admin = admin;
this.eea = eea;
this.websocketService = websocketService; this.websocketService = websocketService;
} }
@ -65,6 +68,10 @@ public class JsonRequestFactories {
return admin; return admin;
} }
public EeaJsonRpcRequestFactory eea() {
return eea;
}
public void shutdown() { public void shutdown() {
netEth.shutdown(); netEth.shutdown();
websocketService.ifPresent(WebSocketService::close); websocketService.ifPresent(WebSocketService::close);

@ -39,4 +39,39 @@ public class ResponseTypes {
public static class RemoveNodeResponse extends Response<String> {} public static class RemoveNodeResponse extends Response<String> {}
public static class GetNodesWhitelistResponse extends Response<List<String>> {} public static class GetNodesWhitelistResponse extends Response<List<String>> {}
public static class PrivateTransactionReceiptResponse
extends Response<PrivateTransactionReceipt> {}
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;
}
}
} }

@ -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.account.Accounts;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.account.TransferTransaction; 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.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.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.PermAddAccountsToWhitelistTransaction;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.perm.PermAddNodeTransaction; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.perm.PermAddNodeTransaction;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.perm.PermGetAccountsWhitelistTransaction; 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); return new TransferTransaction(sender, recipient, String.valueOf(amount), Unit.ETHER, nonce);
} }
public EeaSendRawTransactionTransaction createPrivateRawTransaction(
final String signedRawPrivateTransaction) {
return new EeaSendRawTransactionTransaction(signedRawPrivateTransaction);
}
public TransferTransactionSet createIncrementalTransfers( public TransferTransactionSet createIncrementalTransfers(
final Account sender, final Account recipient, final int etherAmount) { final Account sender, final Account recipient, final int etherAmount) {
final List<TransferTransaction> transfers = new ArrayList<>(); final List<TransferTransaction> transfers = new ArrayList<>();
@ -87,6 +95,15 @@ public class Transactions {
return new EthGetTransactionCountTransaction(accountAddress); 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<String> enodeList) { public PermAddNodeTransaction addNodesToWhitelist(final List<String> enodeList) {
return new PermAddNodeTransaction(enodeList); return new PermAddNodeTransaction(enodeList);
} }

@ -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<PrivateTransactionReceipt> {
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;
}
}
}

@ -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<String> {
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);
}
}
}

@ -28,7 +28,7 @@ public class EthGetTransactionReceiptTransaction
private final String input; private final String input;
EthGetTransactionReceiptTransaction(final String input) { public EthGetTransactionReceiptTransaction(final String input) {
this.input = input; this.input = input;
} }

@ -36,7 +36,7 @@ public class DeployTransactionAcceptanceTest extends AcceptanceTestBase {
public void transactionMustHaveReceipt() { public void transactionMustHaveReceipt() {
final Hash transactionHash = minerNode.execute(transactions.createTransfer(recipient, 5)); final Hash transactionHash = minerNode.execute(transactions.createTransfer(recipient, 5));
cluster.verify(recipient.balanceEquals(5)); cluster.verify(recipient.balanceEquals(5));
minerNode.verify(eth.expectSuccessfulTransactionReceipt(transactionHash)); minerNode.verify(eth.expectSuccessfulTransactionReceipt(transactionHash.toString()));
} }
@Test @Test

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

@ -33,7 +33,7 @@ public class Enclave {
private static final ObjectMapper objectMapper = new ObjectMapper(); private static final ObjectMapper objectMapper = new ObjectMapper();
private static final Logger LOG = LogManager.getLogger(); private static final Logger LOG = LogManager.getLogger();
private String url; private final String url;
private final OkHttpClient client; private final OkHttpClient client;
public Enclave(final String enclaveUrl) { public Enclave(final String enclaveUrl) {

@ -28,11 +28,11 @@ jar {
dependencies { dependencies {
implementation project(':config') implementation project(':config')
implementation project(':crypto') implementation project(':crypto')
implementation project(':enclave')
implementation project(':ethereum:rlp') implementation project(':ethereum:rlp')
implementation project(':ethereum:trie') implementation project(':ethereum:trie')
implementation project(':ethereum:permissioning') implementation project(':ethereum:permissioning')
implementation project(':metrics') implementation project(':metrics')
implementation project(':enclave')
implementation project(':services:kvstore') implementation project(':services:kvstore')
implementation 'com.fasterxml.jackson.core:jackson-databind' implementation 'com.fasterxml.jackson.core:jackson-databind'

@ -18,7 +18,6 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;
import tech.pegasys.orion.testutil.OrionTestHarness; import tech.pegasys.orion.testutil.OrionTestHarness;
import tech.pegasys.pantheon.enclave.Enclave; 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.enclave.types.SendResponse;
import tech.pegasys.pantheon.ethereum.chain.Blockchain; import tech.pegasys.pantheon.ethereum.chain.Blockchain;
import tech.pegasys.pantheon.ethereum.core.Address; 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.ProcessableBlockHeader;
import tech.pegasys.pantheon.ethereum.core.WorldUpdater; import tech.pegasys.pantheon.ethereum.core.WorldUpdater;
import tech.pegasys.pantheon.ethereum.mainnet.SpuriousDragonGasCalculator; 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.privacy.PrivateTransactionProcessor;
import tech.pegasys.pantheon.ethereum.vm.BlockHashLookup; import tech.pegasys.pantheon.ethereum.vm.BlockHashLookup;
import tech.pegasys.pantheon.ethereum.vm.MessageFrame; 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 tech.pegasys.pantheon.util.bytes.BytesValue;
import java.io.IOException; import java.io.IOException;
@ -72,20 +74,22 @@ public class PrivacyPrecompiledContractIntegrationTest {
private static MessageFrame messageFrame; private static MessageFrame messageFrame;
private static OrionTestHarness testHarness; private static OrionTestHarness testHarness;
private static WorldStateArchive worldStateArchive;
private PrivateTransactionProcessor mockPrivateTxProcessor() { private PrivateTransactionProcessor mockPrivateTxProcessor() {
PrivateTransactionProcessor mockPrivateTransactionProcessor = PrivateTransactionProcessor mockPrivateTransactionProcessor =
mock(PrivateTransactionProcessor.class, withSettings().verboseLogging()); mock(PrivateTransactionProcessor.class);
PrivateTransactionProcessor.Result result = PrivateTransactionProcessor.Result result =
PrivateTransactionProcessor.Result.successful( PrivateTransactionProcessor.Result.successful(
null, 0, BytesValue.fromHexString(DEFAULT_OUTPUT), null); null, 0, BytesValue.fromHexString(DEFAULT_OUTPUT), null);
when(mockPrivateTransactionProcessor.processPrivateTransaction( when(mockPrivateTransactionProcessor.processTransaction(
nullable(Blockchain.class), nullable(Blockchain.class),
nullable(WorldUpdater.class), nullable(WorldUpdater.class),
nullable(WorldUpdater.class), nullable(WorldUpdater.class),
nullable(ProcessableBlockHeader.class), nullable(ProcessableBlockHeader.class),
nullable(PrivateTransaction.class), nullable(PrivateTransaction.class),
nullable(Address.class), nullable(Address.class),
nullable(OperationTracer.class),
nullable(BlockHashLookup.class))) nullable(BlockHashLookup.class)))
.thenReturn(result); .thenReturn(result);
@ -100,6 +104,11 @@ public class PrivacyPrecompiledContractIntegrationTest {
enclave = new Enclave(testHarness.clientUrl()); enclave = new Enclave(testHarness.clientUrl());
messageFrame = mock(MessageFrame.class); 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 @AfterClass
@ -122,7 +131,7 @@ public class PrivacyPrecompiledContractIntegrationTest {
PrivacyPrecompiledContract privacyPrecompiledContract = PrivacyPrecompiledContract privacyPrecompiledContract =
new PrivacyPrecompiledContract( new PrivacyPrecompiledContract(
new SpuriousDragonGasCalculator(), publicKeys.get(0), enclave); new SpuriousDragonGasCalculator(), publicKeys.get(0), enclave, worldStateArchive);
privacyPrecompiledContract.setPrivateTransactionProcessor(mockPrivateTxProcessor()); privacyPrecompiledContract.setPrivateTransactionProcessor(mockPrivateTxProcessor());

@ -91,6 +91,7 @@ public class OperationBenchmarkHelper {
.messageFrameStack(messageFrame.getMessageFrameStack()) .messageFrameStack(messageFrame.getMessageFrameStack())
.blockchain(messageFrame.getBlockchain()) .blockchain(messageFrame.getBlockchain())
.worldState(messageFrame.getWorldState()) .worldState(messageFrame.getWorldState())
.privateWorldStates(messageFrame.getPrivateWorldStates())
.initialGas(messageFrame.getRemainingGas()) .initialGas(messageFrame.getRemainingGas())
.address(messageFrame.getContractAddress()) .address(messageFrame.getContractAddress())
.originator(messageFrame.getOriginatorAddress()) .originator(messageFrame.getOriginatorAddress())

@ -14,26 +14,41 @@ package tech.pegasys.pantheon.ethereum.core;
import static java.nio.charset.StandardCharsets.UTF_8; 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.File;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.nio.file.Path;
import com.google.common.io.Files; import com.google.common.io.Files;
public class PrivacyParameters { public class PrivacyParameters {
private static final String ENCLAVE_URL = "http://localhost:8888"; private static final String ENCLAVE_URL = "http://localhost:8888";
public static final URI DEFAULT_ENCLAVE_URL = URI.create(ENCLAVE_URL); public static final URI DEFAULT_ENCLAVE_URL = URI.create(ENCLAVE_URL);
private final String PRIVATE_DATABASE_PATH = "private";
private Integer privacyAddress; private Integer privacyAddress;
private boolean enabled; private boolean enabled;
private String url; private String url;
private String publicKey; private String publicKey;
private File publicKeyFile;
private WorldStateArchive privateWorldStateArchive;
public String getPublicKey() { public String getPublicKey() {
return publicKey; return publicKey;
} }
public File getPublicKeyFile() {
return publicKeyFile;
}
public void setPublicKeyUsingFile(final File publicKeyFile) throws IOException { public void setPublicKeyUsingFile(final File publicKeyFile) throws IOException {
this.publicKeyFile = publicKeyFile;
this.publicKey = Files.asCharSource(publicKeyFile, UTF_8).read(); this.publicKey = Files.asCharSource(publicKeyFile, UTF_8).read();
} }
@ -73,4 +88,17 @@ public class PrivacyParameters {
public void setPrivacyAddress(final Integer privacyAddress) { public void setPrivacyAddress(final Integer privacyAddress) {
this.privacyAddress = 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;
}
} }

@ -236,7 +236,7 @@ public class ProtocolSpecBuilder<T> {
checkNotNull(precompileContractRegistryBuilder, "Missing precompile contract registry"); checkNotNull(precompileContractRegistryBuilder, "Missing precompile contract registry");
checkNotNull(messageCallProcessorBuilder, "Missing message call processor"); checkNotNull(messageCallProcessorBuilder, "Missing message call processor");
checkNotNull(transactionProcessorBuilder, "Missing transaction processor"); checkNotNull(transactionProcessorBuilder, "Missing transaction processor");
checkNotNull(privateTransactionProcessorBuilder, "Missing transaction processor"); checkNotNull(privateTransactionProcessorBuilder, "Missing private transaction processor");
checkNotNull(blockHeaderValidatorBuilder, "Missing block header validator"); checkNotNull(blockHeaderValidatorBuilder, "Missing block header validator");
checkNotNull(blockBodyValidatorBuilder, "Missing block body validator"); checkNotNull(blockBodyValidatorBuilder, "Missing block body validator");
checkNotNull(blockProcessorBuilder, "Missing block processor"); checkNotNull(blockProcessorBuilder, "Missing block processor");

@ -36,7 +36,7 @@ public interface TransactionValidator {
* <p>Note: {@code validate} should be called before getting the sender {@link Account} used in * <p>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}. * 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 sender the sender account state to validate against
* @param maximumNonce the maximum transaction nonce. If not provided the transaction nonce must * @param maximumNonce the maximum transaction nonce. If not provided the transaction nonce must
* equal the sender's current account nonce * equal the sender's current account nonce
@ -57,6 +57,7 @@ public interface TransactionValidator {
INTRINSIC_GAS_EXCEEDS_GAS_LIMIT, INTRINSIC_GAS_EXCEEDS_GAS_LIMIT,
EXCEEDS_BLOCK_GAS_LIMIT, EXCEEDS_BLOCK_GAS_LIMIT,
TX_SENDER_NOT_AUTHORIZED, TX_SENDER_NOT_AUTHORIZED,
CHAIN_HEAD_WORLD_STATE_NOT_AVAILABLE CHAIN_HEAD_WORLD_STATE_NOT_AVAILABLE,
PRIVATE_TRANSACTION_FAILED
} }
} }

@ -27,6 +27,8 @@ import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionProcessor;
import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPInput; import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPInput;
import tech.pegasys.pantheon.ethereum.vm.GasCalculator; import tech.pegasys.pantheon.ethereum.vm.GasCalculator;
import tech.pegasys.pantheon.ethereum.vm.MessageFrame; 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 tech.pegasys.pantheon.util.bytes.BytesValue;
import java.io.IOException; import java.io.IOException;
@ -38,21 +40,29 @@ import org.apache.logging.log4j.Logger;
public class PrivacyPrecompiledContract extends AbstractPrecompiledContract { public class PrivacyPrecompiledContract extends AbstractPrecompiledContract {
private final Enclave enclave; private final Enclave enclave;
private final String enclavePublicKey; private final String enclavePublicKey;
private final WorldStateArchive privateWorldStateArchive;
private PrivateTransactionProcessor privateTransactionProcessor; private PrivateTransactionProcessor privateTransactionProcessor;
private Integer DEFAULT_PRIVACY_GROUP_ID = 0;
private static final Logger LOG = LogManager.getLogger(); private static final Logger LOG = LogManager.getLogger();
public PrivacyPrecompiledContract( public PrivacyPrecompiledContract(
final GasCalculator gasCalculator, final PrivacyParameters privacyParameters) { 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( 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); super("Privacy", gasCalculator);
this.enclave = enclave; this.enclave = enclave;
this.enclavePublicKey = publicKey; this.enclavePublicKey = publicKey;
this.privateWorldStateArchive = worldStateArchive;
} }
public void setPrivateTransactionProcessor( public void setPrivateTransactionProcessor(
@ -76,18 +86,19 @@ public class PrivacyPrecompiledContract extends AbstractPrecompiledContract {
new BytesValueRLPInput( new BytesValueRLPInput(
BytesValue.wrap(Base64.getDecoder().decode(receiveResponse.getPayload())), false); 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 publicWorldState = messageFrame.getWorldState();
WorldUpdater privateWorldState = privateWorldStateArchive.getMutable().updater();
TransactionProcessor.Result result = TransactionProcessor.Result result =
privateTransactionProcessor.processPrivateTransaction( privateTransactionProcessor.processTransaction(
messageFrame.getBlockchain(), messageFrame.getBlockchain(),
privateWorldState,
publicWorldState, publicWorldState,
privateWorldState,
messageFrame.getBlockHeader(), messageFrame.getBlockHeader(),
transaction, privateTransaction,
messageFrame.getMiningBeneficiary(), messageFrame.getMiningBeneficiary(),
OperationTracer.NO_TRACING,
messageFrame.getBlockHashLookup()); messageFrame.getBlockHashLookup());
return result.getOutput(); return result.getOutput();

@ -66,7 +66,6 @@ public class PrivateTransactionHandler {
final BytesValueRLPOutput bvrlp = new BytesValueRLPOutput(); final BytesValueRLPOutput bvrlp = new BytesValueRLPOutput();
privateTransaction.writeTo(bvrlp); privateTransaction.writeTo(bvrlp);
// String s = new String(bvrlp.encoded().extractArray(), UTF_8);
return new SendRequest( return new SendRequest(
Base64.getEncoder().encodeToString(bvrlp.encoded().extractArray()), Base64.getEncoder().encodeToString(bvrlp.encoded().extractArray()),
BytesValues.asString(privateTransaction.getPrivateFrom()), BytesValues.asString(privateTransaction.getPrivateFrom()),

@ -12,8 +12,6 @@
*/ */
package tech.pegasys.pantheon.ethereum.privacy; 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.chain.Blockchain;
import tech.pegasys.pantheon.ethereum.core.Account; import tech.pegasys.pantheon.ethereum.core.Account;
import tech.pegasys.pantheon.ethereum.core.Address; 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.ArrayDeque;
import java.util.Deque; import java.util.Deque;
import java.util.OptionalLong;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
public class PrivateTransactionProcessor implements TransactionProcessor { public class PrivateTransactionProcessor {
private static final Logger LOG = LogManager.getLogger(); private static final Logger LOG = LogManager.getLogger();
@ -55,9 +52,6 @@ public class PrivateTransactionProcessor implements TransactionProcessor {
private final AbstractMessageProcessor messageCallProcessor; private final AbstractMessageProcessor messageCallProcessor;
private MutableAccount sender;
private Address senderAddress;
public static class Result implements TransactionProcessor.Result { public static class Result implements TransactionProcessor.Result {
private final Status status; private final Status status;
@ -144,96 +138,31 @@ public class PrivateTransactionProcessor implements TransactionProcessor {
this.clearEmptyAccounts = clearEmptyAccounts; 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( public Result processTransaction(
final Blockchain blockchain, final Blockchain blockchain,
final WorldUpdater worldState, final WorldUpdater publicWorldState,
final WorldUpdater privateWorldState,
final ProcessableBlockHeader blockHeader, final ProcessableBlockHeader blockHeader,
final Transaction transaction, final PrivateTransaction transaction,
final Address miningBeneficiary, final Address miningBeneficiary,
final OperationTracer operationTracer, final OperationTracer operationTracer,
final BlockHashLookup blockHashLookup) { final BlockHashLookup blockHashLookup) {
LOG.trace("Starting execution of {}", transaction); LOG.trace("Starting private execution of {}", transaction);
ValidationResult<TransactionInvalidReason> 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);
}
validationResult = final Address senderAddress = transaction.getSender();
transactionValidator.validateForSender(transaction, sender, OptionalLong.empty()); final MutableAccount maybePrivateSender = privateWorldState.getMutable(senderAddress);
if (!validationResult.isValid()) { final MutableAccount sender =
LOG.warn("Invalid transaction: {}", validationResult.getErrorMessage()); maybePrivateSender != null
return Result.invalid(validationResult); ? maybePrivateSender
} : resolveAccountFromPublicState(publicWorldState, privateWorldState, senderAddress);
final long previousNonce = sender.incrementNonce(); final long previousNonce = sender.incrementNonce();
LOG.trace( LOG.trace(
"Incremented sender {} nonce ({} -> {})", senderAddress, previousNonce, sender.getNonce()); "Incremented private sender {} nonce ({} -> {})",
final Wei upfrontGasCost = transaction.getUpfrontGasCost();
final Wei previousBalance = sender.decrementBalance(upfrontGasCost);
LOG.trace(
"Deducted sender {} upfront gas cost {} ({} -> {})",
senderAddress, senderAddress,
upfrontGasCost, previousNonce,
previousBalance, sender.getNonce());
sender.getBalance());
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 MessageFrame initialFrame;
final Deque<MessageFrame> messageFrameStack = new ArrayDeque<>(); final Deque<MessageFrame> messageFrameStack = new ArrayDeque<>();
if (transaction.isContractCreation()) { if (transaction.isContractCreation()) {
@ -245,11 +174,11 @@ public class PrivateTransactionProcessor implements TransactionProcessor {
.type(MessageFrame.Type.CONTRACT_CREATION) .type(MessageFrame.Type.CONTRACT_CREATION)
.messageFrameStack(messageFrameStack) .messageFrameStack(messageFrameStack)
.blockchain(blockchain) .blockchain(blockchain)
.worldState(worldUpdater.updater()) .worldState(privateWorldState.updater())
.initialGas(gasAvailable)
.address(contractAddress) .address(contractAddress)
.originator(senderAddress) .originator(senderAddress)
.contract(contractAddress) .contract(contractAddress)
.initialGas(Gas.MAX_VALUE)
.gasPrice(transaction.getGasPrice()) .gasPrice(transaction.getGasPrice())
.inputData(BytesValue.EMPTY) .inputData(BytesValue.EMPTY)
.sender(senderAddress) .sender(senderAddress)
@ -265,18 +194,18 @@ public class PrivateTransactionProcessor implements TransactionProcessor {
} else { } else {
final Address to = transaction.getTo().get(); final Address to = transaction.getTo().get();
final Account contract = worldState.get(to); final Account contract = privateWorldState.get(to);
initialFrame = initialFrame =
MessageFrame.builder() MessageFrame.builder()
.type(MessageFrame.Type.MESSAGE_CALL) .type(MessageFrame.Type.MESSAGE_CALL)
.messageFrameStack(messageFrameStack) .messageFrameStack(messageFrameStack)
.blockchain(blockchain) .blockchain(blockchain)
.worldState(worldUpdater.updater()) .worldState(privateWorldState.updater())
.initialGas(gasAvailable)
.address(to) .address(to)
.originator(senderAddress) .originator(senderAddress)
.contract(to) .contract(to)
.initialGas(Gas.MAX_VALUE)
.gasPrice(transaction.getGasPrice()) .gasPrice(transaction.getGasPrice())
.inputData(transaction.getPayload()) .inputData(transaction.getPayload())
.sender(senderAddress) .sender(senderAddress)
@ -298,47 +227,26 @@ public class PrivateTransactionProcessor implements TransactionProcessor {
} }
if (initialFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { if (initialFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) {
worldUpdater.commit(); privateWorldState.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);
} }
if (initialFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { if (initialFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) {
return Result.successful( return Result.successful(
initialFrame.getLogs(), initialFrame.getLogs(), 0, initialFrame.getOutputData(), ValidationResult.valid());
refunded.toLong(),
initialFrame.getOutputData(),
validationResult);
} else { } 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) { private static void clearEmptyAccounts(final WorldUpdater worldState) {
worldState.getTouchedAccounts().stream() worldState.getTouchedAccounts().stream()
.filter(Account::isEmpty) .filter(Account::isEmpty)

@ -24,9 +24,6 @@ import tech.pegasys.pantheon.ethereum.core.Transaction;
import tech.pegasys.pantheon.ethereum.core.Wei; import tech.pegasys.pantheon.ethereum.core.Wei;
import tech.pegasys.pantheon.ethereum.core.WorldUpdater; import tech.pegasys.pantheon.ethereum.core.WorldUpdater;
import tech.pegasys.pantheon.ethereum.mainnet.AbstractMessageProcessor; 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.Bytes32;
import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.bytes.BytesValue;
import tech.pegasys.pantheon.util.uint.UInt256; 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.Deque;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -185,9 +181,6 @@ public class MessageFrame {
private final WorldUpdater worldState; private final WorldUpdater worldState;
private final Blockchain blockchain; private final Blockchain blockchain;
// Private Global data fields
private HashMap<Integer, WorldUpdater> privateWorldStates;
// Metadata fields. // Metadata fields.
private final Type type; private final Type type;
private State state; private State state;
@ -652,29 +645,6 @@ public class MessageFrame {
return worldState; 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. * Returns the message frame type.
* *

@ -25,6 +25,7 @@ import tech.pegasys.pantheon.enclave.types.ReceiveRequest;
import tech.pegasys.pantheon.enclave.types.ReceiveResponse; import tech.pegasys.pantheon.enclave.types.ReceiveResponse;
import tech.pegasys.pantheon.ethereum.chain.Blockchain; import tech.pegasys.pantheon.ethereum.chain.Blockchain;
import tech.pegasys.pantheon.ethereum.core.Address; 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.ProcessableBlockHeader;
import tech.pegasys.pantheon.ethereum.core.WorldUpdater; import tech.pegasys.pantheon.ethereum.core.WorldUpdater;
import tech.pegasys.pantheon.ethereum.mainnet.SpuriousDragonGasCalculator; 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.privacy.PrivateTransactionProcessor;
import tech.pegasys.pantheon.ethereum.vm.BlockHashLookup; import tech.pegasys.pantheon.ethereum.vm.BlockHashLookup;
import tech.pegasys.pantheon.ethereum.vm.MessageFrame; 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 tech.pegasys.pantheon.util.bytes.BytesValue;
import java.io.IOException; import java.io.IOException;
import java.util.Base64; import java.util.Base64;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TemporaryFolder;
public class PrivacyPrecompiledContractTest { public class PrivacyPrecompiledContractTest {
@Rule public final TemporaryFolder temp = new TemporaryFolder();
private final String actual = "Test String"; private final String actual = "Test String";
private final String publicKey = "public key"; private final String publicKey = "public key";
private final BytesValue key = BytesValue.wrap(actual.getBytes(UTF_8)); private final BytesValue key = BytesValue.wrap(actual.getBytes(UTF_8));
@ -78,13 +85,14 @@ public class PrivacyPrecompiledContractTest {
PrivateTransactionProcessor.Result result = PrivateTransactionProcessor.Result result =
PrivateTransactionProcessor.Result.successful( PrivateTransactionProcessor.Result.successful(
null, 0, BytesValue.fromHexString(DEFAULT_OUTPUT), null); null, 0, BytesValue.fromHexString(DEFAULT_OUTPUT), null);
when(mockPrivateTransactionProcessor.processPrivateTransaction( when(mockPrivateTransactionProcessor.processTransaction(
nullable(Blockchain.class), nullable(Blockchain.class),
nullable(WorldUpdater.class), nullable(WorldUpdater.class),
nullable(WorldUpdater.class), nullable(WorldUpdater.class),
nullable(ProcessableBlockHeader.class), nullable(ProcessableBlockHeader.class),
nullable(PrivateTransaction.class), nullable(PrivateTransaction.class),
nullable(Address.class), nullable(Address.class),
nullable(OperationTracer.class),
nullable(BlockHashLookup.class))) nullable(BlockHashLookup.class)))
.thenReturn(result); .thenReturn(result);
@ -99,12 +107,19 @@ public class PrivacyPrecompiledContractTest {
@Before @Before
public void setUp() throws IOException { 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 = privacyPrecompiledContract =
new PrivacyPrecompiledContract(new SpuriousDragonGasCalculator(), publicKey, mockEnclave()); new PrivacyPrecompiledContract(
new SpuriousDragonGasCalculator(), publicKey, mockEnclave(), worldStateArchive);
privacyPrecompiledContract.setPrivateTransactionProcessor(mockPrivateTxProcessor()); privacyPrecompiledContract.setPrivateTransactionProcessor(mockPrivateTxProcessor());
brokenPrivateTransactionHandler = brokenPrivateTransactionHandler =
new PrivacyPrecompiledContract( new PrivacyPrecompiledContract(
new SpuriousDragonGasCalculator(), publicKey, brokenMockEnclave()); new SpuriousDragonGasCalculator(), publicKey, brokenMockEnclave(), worldStateArchive);
messageFrame = mock(MessageFrame.class); messageFrame = mock(MessageFrame.class);
} }

@ -50,6 +50,23 @@ public class PrivateTransactionTest {
+ "6e766966746a69697a706a52742b4854754642733d8a726573747269637465" + "6e766966746a69697a706a52742b4854754642733d8a726573747269637465"
+ "64"; + "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 = private static final PrivateTransaction VALID_PRIVATE_TRANSACTION =
new PrivateTransaction( new PrivateTransaction(
0L, 0L,
@ -75,6 +92,41 @@ public class PrivateTransactionTest {
BytesValue.wrap("Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs=".getBytes(UTF_8))), BytesValue.wrap("Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs=".getBytes(UTF_8))),
BytesValue.wrap("restricted".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 @Test
public void testWriteTo() { public void testWriteTo() {
BytesValueRLPOutput bvrlpo = new BytesValueRLPOutput(); BytesValueRLPOutput bvrlpo = new BytesValueRLPOutput();
@ -82,6 +134,13 @@ public class PrivateTransactionTest {
assertEquals(VALID_PRIVATE_TRANSACTION_RLP, bvrlpo.encoded().toString()); 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 @Test
public void testReadFrom() { public void testReadFrom() {
PrivateTransaction p = PrivateTransaction p =
@ -91,6 +150,16 @@ public class PrivateTransactionTest {
assertEquals(VALID_PRIVATE_TRANSACTION, p); 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) @Test(expected = RLPException.class)
public void testReadFromInvalid() { public void testReadFromInvalid() {
PrivateTransaction.readFrom( PrivateTransaction.readFrom(

@ -178,16 +178,15 @@ public class DefaultMutableWorldStateTest {
assertEquals(newBalance, worldState.get(ADDRESS).getBalance()); assertEquals(newBalance, worldState.get(ADDRESS).getBalance());
// Check that storage is empty before persisting // Check that storage is empty before persisting
assertEquals(0, storage.entries().count());
assertThat(kvWorldStateStorage.isWorldStateAvailable(worldState.rootHash())).isFalse(); assertThat(kvWorldStateStorage.isWorldStateAvailable(worldState.rootHash())).isFalse();
// Persist and re-run assertions // Persist and re-run assertions
worldState.persist(); worldState.persist();
assertNotEquals(0, storage.entries().count());
assertThat(kvWorldStateStorage.isWorldStateAvailable(worldState.rootHash())).isTrue();
assertEquals(expectedRootHash, worldState.rootHash()); assertEquals(expectedRootHash, worldState.rootHash());
assertNotNull(worldState.get(ADDRESS)); assertNotNull(worldState.get(ADDRESS));
assertEquals(newBalance, worldState.get(ADDRESS).getBalance()); assertEquals(newBalance, worldState.get(ADDRESS).getBalance());
assertThat(kvWorldStateStorage.isWorldStateAvailable(worldState.rootHash())).isTrue();
// Create new world state and check that it can access modified address // Create new world state and check that it can access modified address
final MutableWorldState newWorldState = final MutableWorldState newWorldState =

@ -27,6 +27,7 @@ jar {
dependencies { dependencies {
implementation project(':crypto') implementation project(':crypto')
implementation project(':enclave')
implementation project(':ethereum:blockcreation') implementation project(':ethereum:blockcreation')
implementation project(':ethereum:core') implementation project(':ethereum:core')
implementation project(':ethereum:eth') implementation project(':ethereum:eth')

@ -21,6 +21,7 @@ import tech.pegasys.pantheon.ethereum.blockcreation.EthHashMiningCoordinator;
import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain; import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain;
import tech.pegasys.pantheon.ethereum.core.Block; import tech.pegasys.pantheon.ethereum.core.Block;
import tech.pegasys.pantheon.ethereum.core.BlockImporter; 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.Synchronizer;
import tech.pegasys.pantheon.ethereum.core.TransactionPool; import tech.pegasys.pantheon.ethereum.core.TransactionPool;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.filter.FilterIdGenerator; 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.mainnet.ProtocolSpec;
import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController; 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.ethereum.worldstate.WorldStateArchive;
import tech.pegasys.pantheon.metrics.MetricsSystem; import tech.pegasys.pantheon.metrics.MetricsSystem;
import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem;
@ -82,8 +82,7 @@ public class JsonRpcTestMethodsFactory {
final MetricsSystem metricsSystem = new NoOpMetricsSystem(); final MetricsSystem metricsSystem = new NoOpMetricsSystem();
final Optional<AccountWhitelistController> accountWhitelistController = final Optional<AccountWhitelistController> accountWhitelistController =
Optional.of(mock(AccountWhitelistController.class)); Optional.of(mock(AccountWhitelistController.class));
final PrivateTransactionHandler privateTransactionHandler = final PrivacyParameters privacyParameters = mock(PrivacyParameters.class);
mock(PrivateTransactionHandler.class);
return new JsonRpcMethodsFactory() return new JsonRpcMethodsFactory()
.methods( .methods(
@ -99,6 +98,6 @@ public class JsonRpcTestMethodsFactory {
new HashSet<>(), new HashSet<>(),
accountWhitelistController, accountWhitelistController,
RpcApis.DEFAULT_JSON_RPC_APIS, RpcApis.DEFAULT_JSON_RPC_APIS,
privateTransactionHandler); privacyParameters);
} }
} }

@ -12,8 +12,10 @@
*/ */
package tech.pegasys.pantheon.ethereum.jsonrpc; package tech.pegasys.pantheon.ethereum.jsonrpc;
import tech.pegasys.pantheon.enclave.Enclave;
import tech.pegasys.pantheon.ethereum.blockcreation.MiningCoordinator; import tech.pegasys.pantheon.ethereum.blockcreation.MiningCoordinator;
import tech.pegasys.pantheon.ethereum.chain.Blockchain; 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.Synchronizer;
import tech.pegasys.pantheon.ethereum.core.TransactionPool; import tech.pegasys.pantheon.ethereum.core.TransactionPool;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.filter.FilterManager; 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.PermReloadPermissionsFromFile;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.permissioning.PermRemoveAccountsFromWhitelist; 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.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.methods.privacy.EeaSendRawTransaction;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.BlockReplay; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.BlockReplay;
@ -115,7 +118,7 @@ public class JsonRpcMethodsFactory {
final Collection<RpcApi> rpcApis, final Collection<RpcApi> rpcApis,
final FilterManager filterManager, final FilterManager filterManager,
final Optional<AccountWhitelistController> accountsWhitelistController, final Optional<AccountWhitelistController> accountsWhitelistController,
final PrivateTransactionHandler privateTransactionHandler) { final PrivacyParameters privacyParameters) {
final BlockchainQueries blockchainQueries = final BlockchainQueries blockchainQueries =
new BlockchainQueries(blockchain, worldStateArchive); new BlockchainQueries(blockchain, worldStateArchive);
return methods( return methods(
@ -131,7 +134,7 @@ public class JsonRpcMethodsFactory {
supportedCapabilities, supportedCapabilities,
accountsWhitelistController, accountsWhitelistController,
rpcApis, rpcApis,
privateTransactionHandler); privacyParameters);
} }
public Map<String, JsonRpcMethod> methods( public Map<String, JsonRpcMethod> methods(
@ -147,7 +150,7 @@ public class JsonRpcMethodsFactory {
final Set<Capability> supportedCapabilities, final Set<Capability> supportedCapabilities,
final Optional<AccountWhitelistController> accountsWhitelistController, final Optional<AccountWhitelistController> accountsWhitelistController,
final Collection<RpcApi> rpcApis, final Collection<RpcApi> rpcApis,
final PrivateTransactionHandler privateTransactionHandler) { final PrivacyParameters privacyParameters) {
final Map<String, JsonRpcMethod> enabledMethods = new HashMap<>(); final Map<String, JsonRpcMethod> enabledMethods = new HashMap<>();
// @formatter:off // @formatter:off
if (rpcApis.contains(RpcApis.ETH)) { if (rpcApis.contains(RpcApis.ETH)) {
@ -255,7 +258,12 @@ public class JsonRpcMethodsFactory {
if (rpcApis.contains(RpcApis.EEA)) { if (rpcApis.contains(RpcApis.EEA)) {
addMethods( addMethods(
enabledMethods, 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 // @formatter:off
return enabledMethods; return enabledMethods;

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

@ -12,6 +12,7 @@
*/ */
package tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.privacy; 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 static tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcErrorConverter.convertTransactionInvalidReason;
import tech.pegasys.pantheon.ethereum.core.Transaction; import tech.pegasys.pantheon.ethereum.core.Transaction;
@ -73,6 +74,17 @@ public class EeaSendRawTransaction implements JsonRpcMethod {
return new JsonRpcErrorResponse(request.getId(), JsonRpcError.INVALID_PARAMS); 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; final Transaction transaction;
try { try {
transaction = handlePrivateTransaction(privateTransaction); transaction = handlePrivateTransaction(privateTransaction);

@ -84,7 +84,8 @@ public enum JsonRpcError {
UNAUTHORIZED(-40100, "Unauthorized"), UNAUTHORIZED(-40100, "Unauthorized"),
// Private transaction errors // 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 int code;
private final String message; private final String message;

@ -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<TransactionReceiptLogResult> logReceipts(
final List<Log> logs,
final long blockNumber,
final Hash transactionHash,
final Hash blockHash,
final int transactionIndex) {
final List<TransactionReceiptLogResult> 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;
}
}

@ -27,6 +27,7 @@ import tech.pegasys.pantheon.ethereum.core.Block;
import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import tech.pegasys.pantheon.ethereum.core.BlockImporter; import tech.pegasys.pantheon.ethereum.core.BlockImporter;
import tech.pegasys.pantheon.ethereum.core.PendingTransactions; 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.Synchronizer;
import tech.pegasys.pantheon.ethereum.core.Transaction; import tech.pegasys.pantheon.ethereum.core.Transaction;
import tech.pegasys.pantheon.ethereum.core.TransactionPool; 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.mainnet.ValidationResult;
import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork;
import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; 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.util.RawBlockIterator;
import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive;
import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem;
@ -148,8 +148,7 @@ public abstract class AbstractEthJsonRpcHttpServiceTest {
.thenReturn(ValidationResult.valid()); .thenReturn(ValidationResult.valid());
final PendingTransactions pendingTransactionsMock = mock(PendingTransactions.class); final PendingTransactions pendingTransactionsMock = mock(PendingTransactions.class);
when(transactionPoolMock.getPendingTransactions()).thenReturn(pendingTransactionsMock); when(transactionPoolMock.getPendingTransactions()).thenReturn(pendingTransactionsMock);
final PrivateTransactionHandler privateTransactionHandlerMock = final PrivacyParameters privacyParameters = mock(PrivacyParameters.class);
mock(PrivateTransactionHandler.class);
stateArchive = createInMemoryWorldStateArchive(); stateArchive = createInMemoryWorldStateArchive();
GENESIS_CONFIG.writeStateTo(stateArchive.getMutable()); GENESIS_CONFIG.writeStateTo(stateArchive.getMutable());
@ -183,7 +182,7 @@ public abstract class AbstractEthJsonRpcHttpServiceTest {
supportedCapabilities, supportedCapabilities,
Optional.empty(), Optional.empty(),
JSON_RPC_APIS, JSON_RPC_APIS,
privateTransactionHandlerMock); privacyParameters);
final JsonRpcConfiguration config = JsonRpcConfiguration.createDefault(); final JsonRpcConfiguration config = JsonRpcConfiguration.createDefault();
config.setPort(0); config.setPort(0);
service = service =

@ -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.api.P2PNetwork;
import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController; 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.metrics.noop.NoOpMetricsSystem;
import java.io.IOException; import java.io.IOException;
@ -100,7 +99,7 @@ public class JsonRpcHttpServiceHostWhitelistTest {
supportedCapabilities, supportedCapabilities,
Optional.of(mock(AccountWhitelistController.class)), Optional.of(mock(AccountWhitelistController.class)),
JSON_RPC_APIS, JSON_RPC_APIS,
mock(PrivateTransactionHandler.class))); mock(PrivacyParameters.class)));
service = createJsonRpcHttpService(); service = createJsonRpcHttpService();
service.start().join(); service.start().join();

@ -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.mainnet.MainnetProtocolSchedule;
import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork;
import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability;
import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionHandler;
import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@ -130,7 +129,7 @@ public class JsonRpcHttpServiceLoginTest {
supportedCapabilities, supportedCapabilities,
Optional.empty(), Optional.empty(),
JSON_RPC_APIS, JSON_RPC_APIS,
mock(PrivateTransactionHandler.class))); mock(PrivacyParameters.class)));
service = createJsonRpcHttpService(); service = createJsonRpcHttpService();
jwtAuth = service.authenticationService.get().getJwtAuthProvider(); jwtAuth = service.authenticationService.get().getJwtAuthProvider();
service.start().join(); service.start().join();

@ -18,6 +18,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import tech.pegasys.pantheon.ethereum.blockcreation.EthHashMiningCoordinator; 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.Synchronizer;
import tech.pegasys.pantheon.ethereum.core.TransactionPool; import tech.pegasys.pantheon.ethereum.core.TransactionPool;
import tech.pegasys.pantheon.ethereum.eth.EthProtocol; 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.api.P2PNetwork;
import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController; 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.metrics.noop.NoOpMetricsSystem;
import java.util.HashSet; import java.util.HashSet;
@ -180,7 +180,7 @@ public class JsonRpcHttpServiceRpcApisTest {
supportedCapabilities, supportedCapabilities,
Optional.of(mock(AccountWhitelistController.class)), Optional.of(mock(AccountWhitelistController.class)),
config.getRpcApis(), config.getRpcApis(),
mock(PrivateTransactionHandler.class))); mock(PrivacyParameters.class)));
final JsonRpcHttpService jsonRpcHttpService = final JsonRpcHttpService jsonRpcHttpService =
new JsonRpcHttpService( new JsonRpcHttpService(
vertx, folder.newFolder().toPath(), config, new NoOpMetricsSystem(), rpcMethods); vertx, folder.newFolder().toPath(), config, new NoOpMetricsSystem(), rpcMethods);

@ -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.api.P2PNetwork;
import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController; 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.metrics.noop.NoOpMetricsSystem;
import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.bytes.BytesValue;
import tech.pegasys.pantheon.util.bytes.BytesValues; import tech.pegasys.pantheon.util.bytes.BytesValues;
@ -128,7 +127,7 @@ public class JsonRpcHttpServiceTest {
supportedCapabilities, supportedCapabilities,
Optional.of(mock(AccountWhitelistController.class)), Optional.of(mock(AccountWhitelistController.class)),
JSON_RPC_APIS, JSON_RPC_APIS,
mock(PrivateTransactionHandler.class))); mock(PrivacyParameters.class)));
service = createJsonRpcHttpService(); service = createJsonRpcHttpService();
service.start().join(); service.start().join();

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

@ -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 * 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 * the License. You may obtain a copy of the License at
@ -49,16 +49,14 @@ import org.mockito.junit.MockitoJUnitRunner;
public class EeaSendRawTransactionTest { public class EeaSendRawTransactionTest {
private static final String VALID_PRIVATE_TRANSACTION_RLP = private static final String VALID_PRIVATE_TRANSACTION_RLP =
"0xf90113800182520894095e7baea6a6c7c4c2dfeb977efac326af552d87" "0xf8f5800182520894095e7baea6a6c7c4c2dfeb977efac326af552d87808025a04"
+ "a0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "8b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a3664935"
+ "ffff801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d" + "3a01fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd"
+ "495a36649353a01fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab94" + "2c804ac41316156744d784c4355486d425648586f5a7a7a42675062572f776"
+ "9f53faa07bd2c804ac41316156744d784c4355486d425648586f5a7a7a4267" + "a3561784470573958386c393153476f3df85aac41316156744d784c4355486"
+ "5062572f776a3561784470573958386c393153476f3df85aac41316156744d" + "d425648586f5a7a7a42675062572f776a3561784470573958386c393153476"
+ "784c4355486d425648586f5a7a7a42675062572f776a356178447057395838" + "f3dac4b6f32625671442b6e4e6c4e594c35454537793349644f6e766966746"
+ "6c393153476f3dac4b6f32625671442b6e4e6c4e594c35454537793349644f" + "a69697a706a52742b4854754642733d8c756e72657374726963746564";
+ "6e766966746a69697a706a52742b4854754642733d8a726573747269637465"
+ "64";
private static final Transaction PUBLIC_TRANSACTION = private static final Transaction PUBLIC_TRANSACTION =
new Transaction( new Transaction(
@ -67,9 +65,7 @@ public class EeaSendRawTransactionTest {
21000L, 21000L,
Optional.of( Optional.of(
Address.wrap(BytesValue.fromHexString("0x095e7baea6a6c7c4c2dfeb977efac326af552d87"))), Address.wrap(BytesValue.fromHexString("0x095e7baea6a6c7c4c2dfeb977efac326af552d87"))),
Wei.of( Wei.ZERO,
new BigInteger(
"115792089237316195423570985008687907853269984665640564039457584007913129639935")),
SECP256K1.Signature.create( SECP256K1.Signature.create(
new BigInteger( new BigInteger(
"32886959230931919120748662916110619501838190146643992583529828535682419954515"), "32886959230931919120748662916110619501838190146643992583529828535682419954515"),
@ -161,7 +157,7 @@ public class EeaSendRawTransactionTest {
final JsonRpcResponse expectedResponse = final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse( new JsonRpcSuccessResponse(
request.getId(), "0xa86e8a2324e3abccd52afd6913c4c8a5d91f5d1855c0aa075568416c0a3ff7b2"); request.getId(), "0x221e930a2c18d91fca4d509eaa3512f3e01fef266f660e32473de67474b36c15");
final JsonRpcResponse actualResponse = method.response(request); final JsonRpcResponse actualResponse = method.response(request);

@ -349,9 +349,7 @@ public class StoredMerklePatriciaTrieTest {
assertThat(trie.get(key3)).isEqualTo(Optional.of("value3")); assertThat(trie.get(key3)).isEqualTo(Optional.of("value3"));
// Commit changes to storage, and create new tries from roothash and new storage instance // Commit changes to storage, and create new tries from roothash and new storage instance
assertThat(keyValueStore.entries().count()).isEqualTo(0);
merkleStorage.commit(); merkleStorage.commit();
assertThat(keyValueStore.entries().count()).isGreaterThan(0);
final MerkleStorage newMerkleStorage = new KeyValueMerkleStorage(keyValueStore); final MerkleStorage newMerkleStorage = new KeyValueMerkleStorage(keyValueStore);
trie = trie =
new StoredMerklePatriciaTrie<>( new StoredMerklePatriciaTrie<>(

@ -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.AccountWhitelistController;
import tech.pegasys.pantheon.ethereum.permissioning.NodeWhitelistController; import tech.pegasys.pantheon.ethereum.permissioning.NodeWhitelistController;
import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration; 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.ethereum.worldstate.WorldStateArchive;
import tech.pegasys.pantheon.metrics.MetricsSystem; import tech.pegasys.pantheon.metrics.MetricsSystem;
import tech.pegasys.pantheon.metrics.prometheus.MetricsConfiguration; import tech.pegasys.pantheon.metrics.prometheus.MetricsConfiguration;
@ -259,9 +258,6 @@ public class RunnerBuilder {
}); });
final PrivacyParameters privacyParameters = pantheonController.getPrivacyParameters(); final PrivacyParameters privacyParameters = pantheonController.getPrivacyParameters();
final PrivateTransactionHandler privateTransactionHandler =
new PrivateTransactionHandler(privacyParameters);
final FilterManager filterManager = createFilterManager(vertx, context, transactionPool); final FilterManager filterManager = createFilterManager(vertx, context, transactionPool);
Optional<JsonRpcHttpService> jsonRpcHttpService = Optional.empty(); Optional<JsonRpcHttpService> jsonRpcHttpService = Optional.empty();
@ -280,7 +276,7 @@ public class RunnerBuilder {
jsonRpcConfiguration.getRpcApis(), jsonRpcConfiguration.getRpcApis(),
filterManager, filterManager,
accountWhitelistController, accountWhitelistController,
privateTransactionHandler); privacyParameters);
jsonRpcHttpService = jsonRpcHttpService =
Optional.of( Optional.of(
new JsonRpcHttpService( new JsonRpcHttpService(
@ -303,7 +299,7 @@ public class RunnerBuilder {
webSocketConfiguration.getRpcApis(), webSocketConfiguration.getRpcApis(),
filterManager, filterManager,
accountWhitelistController, accountWhitelistController,
privateTransactionHandler); privacyParameters);
final SubscriptionManager subscriptionManager = final SubscriptionManager subscriptionManager =
createSubscriptionManager( createSubscriptionManager(
@ -363,7 +359,7 @@ public class RunnerBuilder {
final Collection<RpcApi> jsonRpcApis, final Collection<RpcApi> jsonRpcApis,
final FilterManager filterManager, final FilterManager filterManager,
final Optional<AccountWhitelistController> accountWhitelistController, final Optional<AccountWhitelistController> accountWhitelistController,
final PrivateTransactionHandler privateTransactionHandler) { final PrivacyParameters privacyParameters) {
final Map<String, JsonRpcMethod> methods = final Map<String, JsonRpcMethod> methods =
new JsonRpcMethodsFactory() new JsonRpcMethodsFactory()
.methods( .methods(
@ -380,7 +376,7 @@ public class RunnerBuilder {
jsonRpcApis, jsonRpcApis,
filterManager, filterManager,
accountWhitelistController, accountWhitelistController,
privateTransactionHandler); privacyParameters);
methods.putAll(pantheonController.getAdditionalJsonRpcMethods(jsonRpcApis)); methods.putAll(pantheonController.getAdditionalJsonRpcMethods(jsonRpcApis));
return methods; return methods;
} }

@ -446,7 +446,7 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
@Option( @Option(
names = {"--privacy-enabled"}, names = {"--privacy-enabled"},
description = "Enable private transactions (default: ${DEFAULT-VALUE})") description = "Enable private transactions (default: ${DEFAULT-VALUE})")
private final Boolean privacyEnabled = false; private final Boolean isPrivacyEnabled = false;
@Option( @Option(
names = {"--privacy-url"}, names = {"--privacy-url"},
@ -791,13 +791,12 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
logger, logger,
commandLine, commandLine,
"--privacy-enabled", "--privacy-enabled",
!privacyEnabled, !isPrivacyEnabled,
Arrays.asList( Arrays.asList(
"--privacy-url", "--privacy-public-key-file", "--privacy-precompiled-address")); "--privacy-url", "--privacy-public-key-file", "--privacy-precompiled-address"));
final PrivacyParameters privacyParameters = PrivacyParameters.noPrivacy(); final PrivacyParameters privacyParameters = PrivacyParameters.noPrivacy();
if (privacyEnabled) { if (isPrivacyEnabled) {
privacyParameters.setEnabled(privacyEnabled);
privacyParameters.setUrl(privacyUrl.toString()); privacyParameters.setUrl(privacyUrl.toString());
if (privacyPublicKeyFile() != null) { if (privacyPublicKeyFile() != null) {
privacyParameters.setPublicKeyUsingFile(privacyPublicKeyFile()); privacyParameters.setPublicKeyUsingFile(privacyPublicKeyFile());
@ -806,6 +805,7 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
commandLine, "Please specify Enclave public key file path to enable privacy"); commandLine, "Please specify Enclave public key file path to enable privacy");
} }
privacyParameters.setPrivacyAddress(privacyPrecompiledAddress); privacyParameters.setPrivacyAddress(privacyPrecompiledAddress);
privacyParameters.enablePrivateDB(dataDir());
} }
return privacyParameters; return privacyParameters;
} }

@ -22,8 +22,6 @@ import java.util.Set;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class InMemoryKeyValueStorage implements KeyValueStorage { public class InMemoryKeyValueStorage implements KeyValueStorage {
@ -46,21 +44,6 @@ public class InMemoryKeyValueStorage implements KeyValueStorage {
return new InMemoryTransaction(); return new InMemoryTransaction();
} }
@Override
public Stream<Entry> 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 @Override
public void close() {} public void close() {}

@ -19,7 +19,6 @@ import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.io.Closeable; import java.io.Closeable;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Stream;
/** Service provided by pantheon to facilitate persistent data storage. */ /** Service provided by pantheon to facilitate persistent data storage. */
public interface KeyValueStorage extends Closeable { public interface KeyValueStorage extends Closeable {
@ -37,13 +36,6 @@ public interface KeyValueStorage extends Closeable {
*/ */
Transaction startTransaction() throws StorageException; Transaction startTransaction() throws StorageException;
/**
* Stream all stored key-value pairs.
*
* @return A stream of the contained key-value pairs.
*/
Stream<Entry> entries();
class Entry { class Entry {
private final BytesValue key; private final BytesValue key;
private final BytesValue value; private final BytesValue value;

@ -109,14 +109,6 @@ public class RocksDbKeyValueStorage implements KeyValueStorage, Closeable {
return new RocksDbTransaction(db.beginTransaction(options), options); return new RocksDbTransaction(db.beginTransaction(options), options);
} }
@Override
public Stream<Entry> entries() {
throwIfClosed();
final RocksIterator rocksIt = db.newIterator();
rocksIt.seekToFirst();
return new RocksDbEntryIterator(rocksIt).toStream();
}
@Override @Override
public void close() { public void close() {
if (closed.compareAndSet(false, true)) { if (closed.compareAndSet(false, true)) {

@ -16,18 +16,14 @@ import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; 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.services.kvstore.KeyValueStorage.Transaction;
import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.bytes.BytesValue;
import tech.pegasys.pantheon.util.bytes.BytesValues; import tech.pegasys.pantheon.util.bytes.BytesValues;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
@ -97,27 +93,6 @@ public abstract class AbstractKeyValueStorageTest {
assertEquals(Optional.empty(), store.get(BytesValue.fromHexString("0F"))); assertEquals(Optional.empty(), store.get(BytesValue.fromHexString("0F")));
} }
@Test
public void entries() throws Exception {
final KeyValueStorage store = createStore();
final List<Entry> 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<Entry> 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 @Test
public void concurrentUpdate() throws Exception { public void concurrentUpdate() throws Exception {
final int keyCount = 1000; final int keyCount = 1000;

@ -24,9 +24,13 @@ import com.google.common.base.Charsets;
import net.consensys.orion.cmd.Orion; import net.consensys.orion.cmd.Orion;
import net.consensys.orion.config.Config; import net.consensys.orion.config.Config;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class OrionTestHarness { public class OrionTestHarness {
private static final Logger LOG = LogManager.getLogger();
private final Orion orion; private final Orion orion;
private final Config config; private final Config config;
@ -76,6 +80,9 @@ public class OrionTestHarness {
final Orion orion = new Orion(); final Orion orion = new Orion();
orion.run(System.out, System.err, config); 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); return new OrionTestHarness(orion, config);
} }

Loading…
Cancel
Save