[PRIV] Private Transaction Handler (#695)

* Add PrivateTransactionHandler

* Implementing PrivateTransactionHandler

* Send Privacy Marker Transaction to Privacy Precompile Smart Contract

* Tests Handler

* Fix up Private Transaction Handler Test

* Remove wildcard import

* Remove Orion tests from PrivateTransactionHandler

* Fix test exception

* Return error if orion call fails

* Fix PrivateTransactionHandler implementation

- The whole base64 rlp encoded private transaction should be sent to Orion rather than the base64 encoded input of the private transaction.

Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>
pull/2/head
Vinicius Stevam 6 years ago committed by Rob Dawson
parent ece38ec3a6
commit f2438dcf79
  1. 2
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/Transaction.java
  2. 91
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionHandler.java
  3. 126
      ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionHandlerTest.java
  4. 6
      ethereum/jsonrpc/src/integration-test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcTestMethodsFactory.java
  5. 14
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java
  6. 35
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaSendRawTransaction.java
  7. 5
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java
  8. 6
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/AbstractEthJsonRpcHttpServiceTest.java
  9. 4
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceHostWhitelistTest.java
  10. 4
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceRpcApisTest.java
  11. 4
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceTest.java
  12. 87
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaSendRawTransactionTest.java
  13. 4
      orion/src/integration-test/java/tech/pegasys/pantheon/orion/OrionTest.java
  14. 10
      orion/src/main/java/tech/pegasys/pantheon/orion/types/SendRequest.java
  15. 1
      pantheon/build.gradle
  16. 19
      pantheon/src/main/java/tech/pegasys/pantheon/RunnerBuilder.java
  17. 7
      pantheon/src/main/java/tech/pegasys/pantheon/controller/CliquePantheonController.java
  18. 7
      pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftLegacyPantheonController.java
  19. 7
      pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftPantheonController.java
  20. 13
      pantheon/src/main/java/tech/pegasys/pantheon/controller/MainnetPantheonController.java
  21. 5
      pantheon/src/main/java/tech/pegasys/pantheon/controller/PantheonController.java
  22. 10
      pantheon/src/test/java/tech/pegasys/pantheon/RunnerTest.java

@ -125,7 +125,7 @@ public class Transaction {
* <p>The {@code chainId} must be greater than 0 to be applied to a specific chain; otherwise
* it will default to any chain.
*/
protected Transaction(
public Transaction(
final long nonce,
final Wei gasPrice,
final long gasLimit,

@ -0,0 +1,91 @@
/*
* 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.privacy;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.PrivacyParameters;
import tech.pegasys.pantheon.ethereum.core.Transaction;
import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPOutput;
import tech.pegasys.pantheon.orion.Orion;
import tech.pegasys.pantheon.orion.types.SendRequest;
import tech.pegasys.pantheon.orion.types.SendResponse;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import tech.pegasys.pantheon.util.bytes.BytesValues;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Base64;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class PrivateTransactionHandler {
private final Orion orion;
private final Address privacyPrecompileAddress;
public PrivateTransactionHandler(final PrivacyParameters privacyParameters) {
this(
new Orion(privacyParameters.getUrl()),
Address.privacyPrecompiled(privacyParameters.getPrivacyAddress()));
}
public PrivateTransactionHandler(final Orion orion, final Address privacyPrecompileAddress) {
this.orion = orion;
this.privacyPrecompileAddress = privacyPrecompileAddress;
}
public Transaction handle(final PrivateTransaction privateTransaction) throws IOException {
final SendRequest sendRequest = createSendRequest(privateTransaction);
final SendResponse sendResponse;
try {
sendResponse = orion.send(sendRequest);
} catch (IOException e) {
throw e;
}
return createPrivacyMarkerTransaction(sendResponse.getKey(), privateTransaction);
}
private SendRequest createSendRequest(final PrivateTransaction privateTransaction) {
final List<String> privateFor =
privateTransaction
.getPrivateFor()
.stream()
.map(BytesValues::asString)
.collect(Collectors.toList());
final BytesValueRLPOutput bvrlp = new BytesValueRLPOutput();
privateTransaction.writeTo(bvrlp);
return new SendRequest(
Base64.getEncoder().encodeToString(bvrlp.encoded().extractArray()),
BytesValues.asString(privateTransaction.getPrivateFrom()),
privateFor);
}
private Transaction createPrivacyMarkerTransaction(
final String transactionEnclaveKey, final PrivateTransaction privateTransaction) {
return new Transaction(
privateTransaction.getNonce(),
privateTransaction.getGasPrice(),
privateTransaction.getGasLimit(),
Optional.of(privacyPrecompileAddress),
privateTransaction.getValue(),
privateTransaction.getSignature(),
BytesValue.wrap(transactionEnclaveKey.getBytes(Charset.defaultCharset())),
privateTransaction.getSender(),
privateTransaction.getChainId().getAsInt());
}
}

@ -0,0 +1,126 @@
/*
* 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.privacy;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
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.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.Transaction;
import tech.pegasys.pantheon.ethereum.core.Wei;
import tech.pegasys.pantheon.orion.Orion;
import tech.pegasys.pantheon.orion.types.SendRequest;
import tech.pegasys.pantheon.orion.types.SendResponse;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Optional;
import com.google.common.collect.Lists;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class PrivateTransactionHandlerTest {
private static final String TRANSACTION_KEY = "My Transaction Key";
private static final String TRANSACTION_KEY_HEX = "0x4d79205472616e73616374696f6e204b6579";
PrivateTransactionHandler privateTransactionHandler;
PrivateTransactionHandler brokenPrivateTransactionHandler;
private static final PrivateTransaction VALID_PRIVATE_TRANSACTION =
new PrivateTransaction(
0L,
Wei.of(1),
21000L,
Optional.of(
Address.wrap(BytesValue.fromHexString("0x095e7baea6a6c7c4c2dfeb977efac326af552d87"))),
Wei.of(
new BigInteger(
"115792089237316195423570985008687907853269984665640564039457584007913129639935")),
SECP256K1.Signature.create(
new BigInteger(
"32886959230931919120748662916110619501838190146643992583529828535682419954515"),
new BigInteger(
"14473701025599600909210599917245952381483216609124029382871721729679842002948"),
Byte.valueOf("0")),
BytesValue.fromHexString("0x"),
Address.wrap(BytesValue.fromHexString("0x8411b12666f68ef74cace3615c9d5a377729d03f")),
1,
BytesValue.wrap("A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=".getBytes(UTF_8)),
Lists.newArrayList(
BytesValue.wrap("A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=".getBytes(UTF_8)),
BytesValue.wrap("Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs=".getBytes(UTF_8))),
BytesValue.wrap("restricted".getBytes(UTF_8)));
private static final Transaction PUBLIC_TRANSACTION =
new Transaction(
0L,
Wei.of(1),
21000L,
Optional.of(Address.DEFAULT_PRIVACY),
Wei.of(
new BigInteger(
"115792089237316195423570985008687907853269984665640564039457584007913129639935")),
SECP256K1.Signature.create(
new BigInteger(
"32886959230931919120748662916110619501838190146643992583529828535682419954515"),
new BigInteger(
"14473701025599600909210599917245952381483216609124029382871721729679842002948"),
Byte.valueOf("0")),
BytesValue.fromHexString(TRANSACTION_KEY_HEX),
Address.wrap(BytesValue.fromHexString("0x8411b12666f68ef74cace3615c9d5a377729d03f")),
1);
Orion mockOrion() throws IOException {
Orion mockOrion = mock(Orion.class);
SendResponse response = new SendResponse();
response.setKey(TRANSACTION_KEY);
when(mockOrion.send(any(SendRequest.class))).thenReturn(response);
return mockOrion;
}
Orion brokenMockOrion() throws IOException {
Orion mockOrion = mock(Orion.class);
when(mockOrion.send(any(SendRequest.class))).thenThrow(IOException.class);
return mockOrion;
}
@Before
public void setUp() throws IOException {
privateTransactionHandler = new PrivateTransactionHandler(mockOrion(), Address.DEFAULT_PRIVACY);
brokenPrivateTransactionHandler =
new PrivateTransactionHandler(brokenMockOrion(), Address.DEFAULT_PRIVACY);
}
@Test
public void validTransactionThroughHandler() throws IOException {
final Transaction transactionRespose =
privateTransactionHandler.handle(VALID_PRIVATE_TRANSACTION);
assertThat(transactionRespose).isEqualToComparingFieldByField(PUBLIC_TRANSACTION);
}
@Test(expected = IOException.class)
public void enclaveIsDownWhileHandling() throws IOException {
brokenPrivateTransactionHandler.handle(VALID_PRIVATE_TRANSACTION);
}
}

@ -35,6 +35,7 @@ import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule;
import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSpec;
import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController;
import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionHandler;
import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive;
import tech.pegasys.pantheon.metrics.MetricsSystem;
import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem;
@ -81,6 +82,8 @@ public class JsonRpcTestMethodsFactory {
final MetricsSystem metricsSystem = new NoOpMetricsSystem();
final AccountWhitelistController accountWhitelistController =
mock(AccountWhitelistController.class);
final PrivateTransactionHandler privateTransactionHandler =
mock(PrivateTransactionHandler.class);
return new JsonRpcMethodsFactory()
.methods(
@ -95,6 +98,7 @@ public class JsonRpcTestMethodsFactory {
metricsSystem,
new HashSet<>(),
accountWhitelistController,
RpcApis.DEFAULT_JSON_RPC_APIS);
RpcApis.DEFAULT_JSON_RPC_APIS,
privateTransactionHandler);
}
}

@ -85,6 +85,7 @@ import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule;
import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork;
import tech.pegasys.pantheon.ethereum.p2p.wire.Capability;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController;
import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionHandler;
import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive;
import tech.pegasys.pantheon.metrics.MetricsSystem;
@ -111,7 +112,8 @@ public class JsonRpcMethodsFactory {
final Set<Capability> supportedCapabilities,
final Collection<RpcApi> rpcApis,
final FilterManager filterManager,
final AccountWhitelistController accountsWhitelistController) {
final AccountWhitelistController accountsWhitelistController,
final PrivateTransactionHandler privateTransactionHandler) {
final BlockchainQueries blockchainQueries =
new BlockchainQueries(blockchain, worldStateArchive);
return methods(
@ -126,7 +128,8 @@ public class JsonRpcMethodsFactory {
metricsSystem,
supportedCapabilities,
accountsWhitelistController,
rpcApis);
rpcApis,
privateTransactionHandler);
}
public Map<String, JsonRpcMethod> methods(
@ -141,7 +144,8 @@ public class JsonRpcMethodsFactory {
final MetricsSystem metricsSystem,
final Set<Capability> supportedCapabilities,
final AccountWhitelistController accountsWhitelistController,
final Collection<RpcApi> rpcApis) {
final Collection<RpcApi> rpcApis,
final PrivateTransactionHandler privateTransactionHandler) {
final Map<String, JsonRpcMethod> enabledMethods = new HashMap<>();
// @formatter:off
if (rpcApis.contains(RpcApis.ETH)) {
@ -249,7 +253,9 @@ public class JsonRpcMethodsFactory {
new PermRemoveAccountsFromWhitelist(accountsWhitelistController, parameter));
}
if (rpcApis.contains(RpcApis.EEA)) {
addMethods(enabledMethods, new EeaSendRawTransaction(transactionPool, parameter));
addMethods(
enabledMethods,
new EeaSendRawTransaction(privateTransactionHandler, transactionPool, parameter));
}
// @formatter:off
return enabledMethods;

@ -26,10 +26,14 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse;
import tech.pegasys.pantheon.ethereum.mainnet.TransactionValidator.TransactionInvalidReason;
import tech.pegasys.pantheon.ethereum.mainnet.ValidationResult;
import tech.pegasys.pantheon.ethereum.privacy.PrivateTransaction;
import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionHandler;
import tech.pegasys.pantheon.ethereum.rlp.RLP;
import tech.pegasys.pantheon.ethereum.rlp.RLPException;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.io.IOException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -37,11 +41,15 @@ public class EeaSendRawTransaction implements JsonRpcMethod {
private static final Logger LOG = LogManager.getLogger();
private final PrivateTransactionHandler privateTransactionHandler;
private final TransactionPool transactionPool;
private final JsonRpcParameter parameters;
public EeaSendRawTransaction(
final TransactionPool transactionPool, final JsonRpcParameter parameters) {
final PrivateTransactionHandler privateTransactionHandler,
final TransactionPool transactionPool,
final JsonRpcParameter parameters) {
this.privateTransactionHandler = privateTransactionHandler;
this.transactionPool = transactionPool;
this.parameters = parameters;
}
@ -58,13 +66,20 @@ public class EeaSendRawTransaction implements JsonRpcMethod {
}
final String rawPrivateTransaction = parameters.required(request.getParams(), 0, String.class);
final Transaction transaction;
final PrivateTransaction privateTransaction;
try {
transaction = decodeRawTransaction(rawPrivateTransaction);
privateTransaction = decodeRawTransaction(rawPrivateTransaction);
} catch (final InvalidJsonRpcRequestException e) {
return new JsonRpcErrorResponse(request.getId(), JsonRpcError.INVALID_PARAMS);
}
final Transaction transaction;
try {
transaction = handlePrivateTransaction(privateTransaction);
} catch (final InvalidJsonRpcRequestException e) {
return new JsonRpcErrorResponse(request.getId(), JsonRpcError.ENCLAVE_IS_DOWN);
}
final ValidationResult<TransactionInvalidReason> validationResult =
transactionPool.addLocalTransaction(transaction);
return validationResult.either(
@ -74,10 +89,20 @@ public class EeaSendRawTransaction implements JsonRpcMethod {
request.getId(), convertTransactionInvalidReason(errorReason)));
}
private Transaction decodeRawTransaction(final String hash)
private Transaction handlePrivateTransaction(final PrivateTransaction privateTransaction)
throws InvalidJsonRpcRequestException {
try {
return privateTransactionHandler.handle(privateTransaction);
} catch (final IOException e) {
LOG.debug(e);
throw new InvalidJsonRpcRequestException("Unable to handle private transaction", e);
}
}
private PrivateTransaction decodeRawTransaction(final String hash)
throws InvalidJsonRpcRequestException {
try {
return Transaction.readFrom(RLP.input(BytesValue.fromHexString(hash)));
return PrivateTransaction.readFrom(RLP.input(BytesValue.fromHexString(hash)));
} catch (final IllegalArgumentException | RLPException e) {
LOG.debug(e);
throw new InvalidJsonRpcRequestException("Invalid raw private transaction hex", e);

@ -61,7 +61,10 @@ public enum JsonRpcError {
NODE_WHITELIST_DUPLICATED_ENTRY(-32000, "Request can't contain duplicated node entries"),
NODE_WHITELIST_EXISTING_ENTRY(-32000, "Node whitelist can't contain duplicated node entries"),
NODE_WHITELIST_MISSING_ENTRY(-32000, "Node whitelist does not contain a specified node"),
NODE_WHITELIST_INVALID_ENTRY(-32000, "Unable to add invalid node to the node whitelist");
NODE_WHITELIST_INVALID_ENTRY(-32000, "Unable to add invalid node to the node whitelist"),
// Private transaction errors
ENCLAVE_IS_DOWN(-32000, "Enclave is down");
private final int code;
private final String message;

@ -47,6 +47,7 @@ import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork;
import tech.pegasys.pantheon.ethereum.p2p.wire.Capability;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController;
import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration;
import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionHandler;
import tech.pegasys.pantheon.ethereum.util.RawBlockIterator;
import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive;
import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem;
@ -149,6 +150,8 @@ public abstract class AbstractEthJsonRpcHttpServiceTest {
.thenReturn(ValidationResult.valid());
final PendingTransactions pendingTransactionsMock = mock(PendingTransactions.class);
when(transactionPoolMock.getPendingTransactions()).thenReturn(pendingTransactionsMock);
final PrivateTransactionHandler privateTransactionHandlerMock =
mock(PrivateTransactionHandler.class);
stateArchive = createInMemoryWorldStateArchive();
GENESIS_CONFIG.writeStateTo(stateArchive.getMutable(Hash.EMPTY_TRIE_HASH));
@ -184,7 +187,8 @@ public abstract class AbstractEthJsonRpcHttpServiceTest {
new NoOpMetricsSystem(),
supportedCapabilities,
accountWhitelistController,
JSON_RPC_APIS);
JSON_RPC_APIS,
privateTransactionHandlerMock);
final JsonRpcConfiguration config = JsonRpcConfiguration.createDefault();
config.setPort(0);
service =

@ -29,6 +29,7 @@ import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule;
import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork;
import tech.pegasys.pantheon.ethereum.p2p.wire.Capability;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController;
import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionHandler;
import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem;
import java.io.IOException;
@ -97,7 +98,8 @@ public class JsonRpcHttpServiceHostWhitelistTest {
new NoOpMetricsSystem(),
supportedCapabilities,
mock(AccountWhitelistController.class),
JSON_RPC_APIS));
JSON_RPC_APIS,
mock(PrivateTransactionHandler.class)));
service = createJsonRpcHttpService();
service.start().join();

@ -29,6 +29,7 @@ import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule;
import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork;
import tech.pegasys.pantheon.ethereum.p2p.wire.Capability;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController;
import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionHandler;
import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem;
import java.util.HashSet;
@ -177,7 +178,8 @@ public class JsonRpcHttpServiceRpcApisTest {
new NoOpMetricsSystem(),
supportedCapabilities,
mock(AccountWhitelistController.class),
config.getRpcApis()));
config.getRpcApis(),
mock(PrivateTransactionHandler.class)));
final JsonRpcHttpService jsonRpcHttpService =
new JsonRpcHttpService(
vertx, folder.newFolder().toPath(), config, new NoOpMetricsSystem(), rpcMethods);

@ -44,6 +44,7 @@ import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule;
import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork;
import tech.pegasys.pantheon.ethereum.p2p.wire.Capability;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController;
import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionHandler;
import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import tech.pegasys.pantheon.util.bytes.BytesValues;
@ -126,7 +127,8 @@ public class JsonRpcHttpServiceTest {
new NoOpMetricsSystem(),
supportedCapabilities,
mock(AccountWhitelistController.class),
JSON_RPC_APIS));
JSON_RPC_APIS,
mock(PrivateTransactionHandler.class)));
service = createJsonRpcHttpService();
service.start().join();

@ -18,8 +18,11 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import tech.pegasys.pantheon.crypto.SECP256K1;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.Transaction;
import tech.pegasys.pantheon.ethereum.core.TransactionPool;
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.response.JsonRpcError;
@ -28,6 +31,13 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse;
import tech.pegasys.pantheon.ethereum.mainnet.TransactionValidator.TransactionInvalidReason;
import tech.pegasys.pantheon.ethereum.mainnet.ValidationResult;
import tech.pegasys.pantheon.ethereum.privacy.PrivateTransaction;
import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionHandler;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
@ -38,17 +48,49 @@ import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class EeaSendRawTransactionTest {
private static final String VALID_TRANSACTION =
"0xf86d0485174876e800830222e0945aae326516b4f8fe08074b7e972e40a713048d62880de0b6b3a7640000801ba05d4e7998757264daab67df2ce6f7e7a0ae36910778a406ca73898c9899a32b9ea0674700d5c3d1d27f2e6b4469957dfd1a1c49bf92383d80717afc84eb05695d5b";
private static final String VALID_PRIVATE_TRANSACTION_RLP =
"0xf90113800182520894095e7baea6a6c7c4c2dfeb977efac326af552d87"
+ "a0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+ "ffff801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d"
+ "495a36649353a01fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab94"
+ "9f53faa07bd2c804ac41316156744d784c4355486d425648586f5a7a7a4267"
+ "5062572f776a3561784470573958386c393153476f3df85aac41316156744d"
+ "784c4355486d425648586f5a7a7a42675062572f776a356178447057395838"
+ "6c393153476f3dac4b6f32625671442b6e4e6c4e594c35454537793349644f"
+ "6e766966746a69697a706a52742b4854754642733d8a726573747269637465"
+ "64";
private static final Transaction PUBLIC_TRANSACTION =
new Transaction(
0L,
Wei.of(1),
21000L,
Optional.of(
Address.wrap(BytesValue.fromHexString("0x095e7baea6a6c7c4c2dfeb977efac326af552d87"))),
Wei.of(
new BigInteger(
"115792089237316195423570985008687907853269984665640564039457584007913129639935")),
SECP256K1.Signature.create(
new BigInteger(
"32886959230931919120748662916110619501838190146643992583529828535682419954515"),
new BigInteger(
"14473701025599600909210599917245952381483216609124029382871721729679842002948"),
Byte.valueOf("0")),
BytesValue.fromHexString("0x"),
Address.wrap(BytesValue.fromHexString("0x8411b12666f68ef74cace3615c9d5a377729d03f")),
0);
@Mock private TransactionPool transactionPool;
@Mock private JsonRpcParameter parameter;
private EeaSendRawTransaction method;
@Mock private EeaSendRawTransaction method;
@Mock private PrivateTransactionHandler privateTxHandler;
@Before
public void before() {
method = new EeaSendRawTransaction(transactionPool, parameter);
method = new EeaSendRawTransaction(privateTxHandler, transactionPool, parameter);
}
@Test
@ -81,76 +123,84 @@ public class EeaSendRawTransactionTest {
}
@Test
public void validTransactionIsSentToTransactionPool() {
when(parameter.required(any(Object[].class), anyInt(), any())).thenReturn(VALID_TRANSACTION);
public void validTransactionIsSentToTransactionPool() throws IOException {
when(parameter.required(any(Object[].class), anyInt(), any()))
.thenReturn(VALID_PRIVATE_TRANSACTION_RLP);
when(privateTxHandler.handle(any(PrivateTransaction.class))).thenReturn(PUBLIC_TRANSACTION);
when(transactionPool.addLocalTransaction(any(Transaction.class)))
.thenReturn(ValidationResult.valid());
final JsonRpcRequest request =
new JsonRpcRequest("2.0", "eea_sendRawTransaction", new String[] {VALID_TRANSACTION});
new JsonRpcRequest(
"2.0", "eea_sendRawTransaction", new String[] {VALID_PRIVATE_TRANSACTION_RLP});
final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(
request.getId(), "0xbaabcc1bd699e7378451e4ce5969edb9bdcae76cb79bdacae793525c31e423c7");
request.getId(), "0xa86e8a2324e3abccd52afd6913c4c8a5d91f5d1855c0aa075568416c0a3ff7b2");
final JsonRpcResponse actualResponse = method.response(request);
assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse);
verify(privateTxHandler).handle(any(PrivateTransaction.class));
verify(transactionPool).addLocalTransaction(any(Transaction.class));
}
@Test
public void transactionWithNonceBelowAccountNonceIsRejected() {
public void transactionWithNonceBelowAccountNonceIsRejected() throws IOException {
verifyErrorForInvalidTransaction(
TransactionInvalidReason.NONCE_TOO_LOW, JsonRpcError.NONCE_TOO_LOW);
}
@Test
public void transactionWithNonceAboveAccountNonceIsRejected() {
public void transactionWithNonceAboveAccountNonceIsRejected() throws IOException {
verifyErrorForInvalidTransaction(
TransactionInvalidReason.INCORRECT_NONCE, JsonRpcError.INCORRECT_NONCE);
}
@Test
public void transactionWithInvalidSignatureIsRejected() {
public void transactionWithInvalidSignatureIsRejected() throws IOException {
verifyErrorForInvalidTransaction(
TransactionInvalidReason.INVALID_SIGNATURE, JsonRpcError.INVALID_TRANSACTION_SIGNATURE);
}
@Test
public void transactionWithIntrinsicGasExceedingGasLimitIsRejected() {
public void transactionWithIntrinsicGasExceedingGasLimitIsRejected() throws IOException {
verifyErrorForInvalidTransaction(
TransactionInvalidReason.INTRINSIC_GAS_EXCEEDS_GAS_LIMIT,
JsonRpcError.INTRINSIC_GAS_EXCEEDS_LIMIT);
}
@Test
public void transactionWithUpfrontGasExceedingAccountBalanceIsRejected() {
public void transactionWithUpfrontGasExceedingAccountBalanceIsRejected() throws IOException {
verifyErrorForInvalidTransaction(
TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE,
JsonRpcError.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE);
}
@Test
public void transactionWithGasLimitExceedingBlockGasLimitIsRejected() {
public void transactionWithGasLimitExceedingBlockGasLimitIsRejected() throws IOException {
verifyErrorForInvalidTransaction(
TransactionInvalidReason.EXCEEDS_BLOCK_GAS_LIMIT, JsonRpcError.EXCEEDS_BLOCK_GAS_LIMIT);
}
@Test
public void transactionWithNotWhitelistedSenderAccountIsRejected() {
public void transactionWithNotWhitelistedSenderAccountIsRejected() throws IOException {
verifyErrorForInvalidTransaction(
TransactionInvalidReason.TX_SENDER_NOT_AUTHORIZED, JsonRpcError.TX_SENDER_NOT_AUTHORIZED);
}
private void verifyErrorForInvalidTransaction(
final TransactionInvalidReason transactionInvalidReason, final JsonRpcError expectedError) {
when(parameter.required(any(Object[].class), anyInt(), any())).thenReturn(VALID_TRANSACTION);
final TransactionInvalidReason transactionInvalidReason, final JsonRpcError expectedError)
throws IOException {
when(parameter.required(any(Object[].class), anyInt(), any()))
.thenReturn(VALID_PRIVATE_TRANSACTION_RLP);
when(privateTxHandler.handle(any(PrivateTransaction.class))).thenReturn(PUBLIC_TRANSACTION);
when(transactionPool.addLocalTransaction(any(Transaction.class)))
.thenReturn(ValidationResult.invalid(transactionInvalidReason));
final JsonRpcRequest request =
new JsonRpcRequest("2.0", "eea_sendRawTransaction", new String[] {VALID_TRANSACTION});
new JsonRpcRequest(
"2.0", "eea_sendRawTransaction", new String[] {VALID_PRIVATE_TRANSACTION_RLP});
final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(request.getId(), expectedError);
@ -158,6 +208,7 @@ public class EeaSendRawTransactionTest {
final JsonRpcResponse actualResponse = method.response(request);
assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse);
verify(privateTxHandler).handle(any(PrivateTransaction.class));
verify(transactionPool).addLocalTransaction(any(Transaction.class));
}

@ -25,6 +25,7 @@ import tech.pegasys.pantheon.orion.types.SendResponse;
import java.io.IOException;
import java.util.List;
import com.google.common.collect.Lists;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.ClassRule;
@ -63,7 +64,8 @@ public class OrionTest {
public void testSendAndReceive() throws IOException {
List<String> publicKeys = testHarness.getPublicKeys();
SendRequest sc = new SendRequest(PAYLOAD, publicKeys.get(0), new String[] {publicKeys.get(1)});
SendRequest sc =
new SendRequest(PAYLOAD, publicKeys.get(0), Lists.newArrayList(publicKeys.get(1)));
SendResponse sr = orion.send(sc);
ReceiveRequest rc = new ReceiveRequest(sr.getKey(), publicKeys.get(1));

@ -14,12 +14,14 @@ package tech.pegasys.pantheon.orion.types;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.util.List;
public class SendRequest {
private byte[] payload;
private String from;
private String[] to;
private List<String> to;
public SendRequest(final String payload, final String from, final String[] to) {
public SendRequest(final String payload, final String from, final List<String> to) {
this.payload = payload.getBytes(UTF_8);
this.from = from;
this.to = to;
@ -41,11 +43,11 @@ public class SendRequest {
this.from = from;
}
public String[] getTo() {
public List<String> getTo() {
return to;
}
public void setTo(final String[] to) {
public void setTo(final List<String> to) {
this.to = to;
}
}

@ -40,6 +40,7 @@ dependencies {
implementation project(':ethereum:p2p')
implementation project(':ethereum:rlp')
implementation project(':metrics')
implementation project(':orion')
implementation project(':services:kvstore')
implementation 'com.google.guava:guava'

@ -17,6 +17,7 @@ import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.ethereum.ProtocolContext;
import tech.pegasys.pantheon.ethereum.blockcreation.MiningCoordinator;
import tech.pegasys.pantheon.ethereum.chain.Blockchain;
import tech.pegasys.pantheon.ethereum.core.PrivacyParameters;
import tech.pegasys.pantheon.ethereum.core.Synchronizer;
import tech.pegasys.pantheon.ethereum.core.TransactionPool;
import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration;
@ -53,6 +54,7 @@ import tech.pegasys.pantheon.ethereum.p2p.wire.Capability;
import tech.pegasys.pantheon.ethereum.p2p.wire.SubProtocol;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController;
import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration;
import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionHandler;
import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive;
import tech.pegasys.pantheon.metrics.MetricsSystem;
import tech.pegasys.pantheon.metrics.prometheus.MetricsConfiguration;
@ -239,13 +241,16 @@ public class RunnerBuilder {
final TransactionPool transactionPool = pantheonController.getTransactionPool();
final MiningCoordinator miningCoordinator = pantheonController.getMiningCoordinator();
final AccountWhitelistController accountWhitelistController =
new AccountWhitelistController(permissioningConfiguration);
if (permissioningConfiguration.isAccountWhitelistSet()) {
transactionPool.setAccountWhitelist(accountWhitelistController);
}
final PrivacyParameters privacyParameters = pantheonController.getPrivacyParameters();
final PrivateTransactionHandler privateTransactionHandler =
new PrivateTransactionHandler(privacyParameters);
final FilterManager filterManager = createFilterManager(vertx, context, transactionPool);
Optional<JsonRpcHttpService> jsonRpcHttpService = Optional.empty();
@ -263,7 +268,8 @@ public class RunnerBuilder {
supportedCapabilities,
jsonRpcConfiguration.getRpcApis(),
filterManager,
accountWhitelistController);
accountWhitelistController,
privateTransactionHandler);
jsonRpcHttpService =
Optional.of(
new JsonRpcHttpService(
@ -285,7 +291,8 @@ public class RunnerBuilder {
supportedCapabilities,
webSocketConfiguration.getRpcApis(),
filterManager,
accountWhitelistController);
accountWhitelistController,
privateTransactionHandler);
final SubscriptionManager subscriptionManager =
createSubscriptionManager(
@ -344,7 +351,8 @@ public class RunnerBuilder {
final Set<Capability> supportedCapabilities,
final Collection<RpcApi> jsonRpcApis,
final FilterManager filterManager,
final AccountWhitelistController accountWhitelistController) {
final AccountWhitelistController accountWhitelistController,
final PrivateTransactionHandler privateTransactionHandler) {
final Map<String, JsonRpcMethod> methods =
new JsonRpcMethodsFactory()
.methods(
@ -360,7 +368,8 @@ public class RunnerBuilder {
supportedCapabilities,
jsonRpcApis,
filterManager,
accountWhitelistController);
accountWhitelistController,
privateTransactionHandler);
methods.putAll(pantheonController.getAdditionalJsonRpcMethods(jsonRpcApis));
return methods;
}

@ -38,6 +38,7 @@ import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.core.MiningParameters;
import tech.pegasys.pantheon.ethereum.core.PrivacyParameters;
import tech.pegasys.pantheon.ethereum.core.Synchronizer;
import tech.pegasys.pantheon.ethereum.core.TransactionPool;
import tech.pegasys.pantheon.ethereum.core.Util;
@ -254,6 +255,12 @@ public class CliquePantheonController implements PantheonController<CliqueContex
return miningCoordinator;
}
@Override
public PrivacyParameters getPrivacyParameters() {
LOG.warn("CliquePantheonController does not currently support private transactions.");
return PrivacyParameters.noPrivacy();
}
@Override
public Map<String, JsonRpcMethod> getAdditionalJsonRpcMethods(
final Collection<RpcApi> enabledRpcApis) {

@ -35,6 +35,7 @@ import tech.pegasys.pantheon.ethereum.chain.DefaultMutableBlockchain;
import tech.pegasys.pantheon.ethereum.chain.GenesisState;
import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.core.PrivacyParameters;
import tech.pegasys.pantheon.ethereum.core.Synchronizer;
import tech.pegasys.pantheon.ethereum.core.TransactionPool;
import tech.pegasys.pantheon.ethereum.eth.EthProtocol;
@ -235,6 +236,12 @@ public class IbftLegacyPantheonController implements PantheonController<IbftCont
return null;
}
@Override
public PrivacyParameters getPrivacyParameters() {
LOG.warn("IbftLegacyPantheonController does not currently support private transactions.");
return PrivacyParameters.noPrivacy();
}
@Override
public Map<String, JsonRpcMethod> getAdditionalJsonRpcMethods(
final Collection<RpcApi> enabledRpcApis) {

@ -54,6 +54,7 @@ import tech.pegasys.pantheon.ethereum.chain.MinedBlockObserver;
import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.core.MiningParameters;
import tech.pegasys.pantheon.ethereum.core.PrivacyParameters;
import tech.pegasys.pantheon.ethereum.core.Synchronizer;
import tech.pegasys.pantheon.ethereum.core.TransactionPool;
import tech.pegasys.pantheon.ethereum.core.Util;
@ -322,6 +323,12 @@ public class IbftPantheonController implements PantheonController<IbftContext> {
return null;
}
@Override
public PrivacyParameters getPrivacyParameters() {
LOG.warn("IbftPantheonController does not currently support private transactions.");
return PrivacyParameters.noPrivacy();
}
@Override
public Map<String, JsonRpcMethod> getAdditionalJsonRpcMethods(
final Collection<RpcApi> enabledRpcApis) {

@ -25,6 +25,7 @@ import tech.pegasys.pantheon.ethereum.chain.GenesisState;
import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.core.MiningParameters;
import tech.pegasys.pantheon.ethereum.core.PrivacyParameters;
import tech.pegasys.pantheon.ethereum.core.Synchronizer;
import tech.pegasys.pantheon.ethereum.core.TransactionPool;
import tech.pegasys.pantheon.ethereum.eth.EthProtocol;
@ -66,6 +67,7 @@ public class MainnetPantheonController implements PantheonController<Void> {
private final TransactionPool transactionPool;
private final MiningCoordinator miningCoordinator;
private final PrivacyParameters privacyParameters;
private final Runnable close;
public MainnetPantheonController(
@ -76,6 +78,7 @@ public class MainnetPantheonController implements PantheonController<Void> {
final KeyPair keyPair,
final TransactionPool transactionPool,
final MiningCoordinator miningCoordinator,
final PrivacyParameters privacyParameters,
final Runnable close) {
this.protocolSchedule = protocolSchedule;
this.protocolContext = protocolContext;
@ -84,6 +87,7 @@ public class MainnetPantheonController implements PantheonController<Void> {
this.keyPair = keyPair;
this.transactionPool = transactionPool;
this.miningCoordinator = miningCoordinator;
this.privacyParameters = privacyParameters;
this.close = close;
}
@ -94,7 +98,8 @@ public class MainnetPantheonController implements PantheonController<Void> {
final SynchronizerConfiguration taintedSyncConfig,
final MiningParameters miningParams,
final KeyPair nodeKeys,
final MetricsSystem metricsSystem) {
final MetricsSystem metricsSystem,
final PrivacyParameters privacyParameters) {
final GenesisState genesisState = GenesisState.fromConfig(genesisConfig, protocolSchedule);
final BlockchainStorage blockchainStorage =
@ -168,6 +173,7 @@ public class MainnetPantheonController implements PantheonController<Void> {
nodeKeys,
transactionPool,
miningCoordinator,
privacyParameters,
() -> {
miningCoordinator.disable();
minerThreadPool.shutdownNow();
@ -223,4 +229,9 @@ public class MainnetPantheonController implements PantheonController<Void> {
public void close() {
close.run();
}
@Override
public PrivacyParameters getPrivacyParameters() {
return privacyParameters;
}
}

@ -61,7 +61,8 @@ public interface PantheonController<C> extends Closeable {
syncConfig,
miningParameters,
nodeKeys,
metricsSystem);
metricsSystem,
privacyParameters);
} else if (configOptions.isRevisedIbft()) {
return IbftPantheonController.init(
storageProvider,
@ -108,6 +109,8 @@ public interface PantheonController<C> extends Closeable {
MiningCoordinator getMiningCoordinator();
PrivacyParameters getPrivacyParameters();
default Map<String, JsonRpcMethod> getAdditionalJsonRpcMethods(
final Collection<RpcApi> enabledRpcApis) {
return emptyMap();

@ -25,6 +25,7 @@ import tech.pegasys.pantheon.ethereum.core.BlockImporter;
import tech.pegasys.pantheon.ethereum.core.BlockSyncTestUtils;
import tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider;
import tech.pegasys.pantheon.ethereum.core.MiningParametersTestBuilder;
import tech.pegasys.pantheon.ethereum.core.PrivacyParameters;
import tech.pegasys.pantheon.ethereum.eth.sync.SyncMode;
import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration;
import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration;
@ -109,7 +110,8 @@ public final class RunnerTest {
fastSyncConfig,
new MiningParametersTestBuilder().enabled(false).build(),
aheadDbNodeKeys,
noOpMetricsSystem)) {
noOpMetricsSystem,
PrivacyParameters.noPrivacy())) {
setupState(blockCount, controller.getProtocolSchedule(), controller.getProtocolContext());
}
@ -122,7 +124,8 @@ public final class RunnerTest {
fastSyncConfig,
new MiningParametersTestBuilder().enabled(false).build(),
aheadDbNodeKeys,
noOpMetricsSystem);
noOpMetricsSystem,
PrivacyParameters.noPrivacy());
final String listenHost = InetAddress.getLoopbackAddress().getHostAddress();
final ExecutorService executorService = Executors.newFixedThreadPool(2);
final JsonRpcConfiguration aheadJsonRpcConfiguration = jsonRpcConfiguration();
@ -165,7 +168,8 @@ public final class RunnerTest {
fastSyncConfig,
new MiningParametersTestBuilder().enabled(false).build(),
KeyPair.generate(),
noOpMetricsSystem);
noOpMetricsSystem,
PrivacyParameters.noPrivacy());
final Runner runnerBehind =
runnerBuilder
.pantheonController(controllerBehind)

Loading…
Cancel
Save