From 313f48b7dc693da507ccf5871b2c72daaf779edd Mon Sep 17 00:00:00 2001 From: Trent Mohay <37158202+rain-on@users.noreply.github.com> Date: Mon, 26 Aug 2019 13:08:50 +1000 Subject: [PATCH] Added eea_getTransactionCount Json Rpc (#1861) Exposers a Json Rpc which allows "legacy" users to query for the nonce of a given address, in a group of private users. --- .../pegasys/pantheon/enclave/Enclave.java | 48 +++++--- .../PrivacyPrecompiledContractTest.java | 10 +- .../PrivateTransactionHandlerTest.java | 17 +-- .../jsonrpc/JsonRpcMethodsFactory.java | 6 +- .../pantheon/ethereum/jsonrpc/RpcMethod.java | 1 + .../privacy/eea/EeaGetTransactionCount.java | 67 +++++++++++ .../privacy/eea/EeaPrivateNonceProvider.java | 62 +++++++++++ .../internal/response/JsonRpcError.java | 1 + .../eea/EeaGetTransactionCountTest.java | 92 ++++++++++++++++ .../eea/EeaPrivateNonceProviderTest.java | 104 ++++++++++++++++++ 10 files changed, 380 insertions(+), 28 deletions(-) create mode 100644 ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/eea/EeaGetTransactionCount.java create mode 100644 ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/eea/EeaPrivateNonceProvider.java create mode 100644 ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/eea/EeaGetTransactionCountTest.java create mode 100644 ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/eea/EeaPrivateNonceProviderTest.java diff --git a/enclave/src/main/java/tech/pegasys/pantheon/enclave/Enclave.java b/enclave/src/main/java/tech/pegasys/pantheon/enclave/Enclave.java index 553034d1c4..52af42674b 100644 --- a/enclave/src/main/java/tech/pegasys/pantheon/enclave/Enclave.java +++ b/enclave/src/main/java/tech/pegasys/pantheon/enclave/Enclave.java @@ -25,6 +25,9 @@ import java.io.IOException; import java.net.URI; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import okhttp3.MediaType; import okhttp3.OkHttpClient; @@ -60,47 +63,62 @@ public boolean upCheck() throws IOException { } } - public SendResponse send(final SendRequest content) throws Exception { + public SendResponse send(final SendRequest content) { return executePost(buildPostRequest(JSON, content, "/send"), SendResponse.class); } - public ReceiveResponse receive(final ReceiveRequest content) throws Exception { + public ReceiveResponse receive(final ReceiveRequest content) { return executePost(buildPostRequest(ORION, content, "/receive"), ReceiveResponse.class); } - public PrivacyGroup createPrivacyGroup(final CreatePrivacyGroupRequest content) throws Exception { + public PrivacyGroup createPrivacyGroup(final CreatePrivacyGroupRequest content) { return executePost(buildPostRequest(JSON, content, "/createPrivacyGroup"), PrivacyGroup.class); } - public String deletePrivacyGroup(final DeletePrivacyGroupRequest content) throws Exception { + public String deletePrivacyGroup(final DeletePrivacyGroupRequest content) { return executePost(buildPostRequest(JSON, content, "/deletePrivacyGroup"), String.class); } - public PrivacyGroup[] findPrivacyGroup(final FindPrivacyGroupRequest content) throws Exception { + public PrivacyGroup[] findPrivacyGroup(final FindPrivacyGroupRequest content) { Request request = buildPostRequest(JSON, content, "/findPrivacyGroup"); return executePost(request, PrivacyGroup[].class); } private Request buildPostRequest( - final MediaType mediaType, final Object content, final String endpoint) throws Exception { - final RequestBody body = - RequestBody.create(mediaType, objectMapper.writeValueAsString(content)); + final MediaType mediaType, final Object content, final String endpoint) { + final RequestBody body; + try { + body = RequestBody.create(mediaType, objectMapper.writeValueAsString(content)); + } catch (final JsonProcessingException e) { + throw new EnclaveException("Failed to serialise request to json body."); + } final String url = enclaveUri.resolve(endpoint).toString(); return new Request.Builder().url(url).post(body).build(); } - private T executePost(final Request request, final Class responseType) throws Exception { - try (Response response = client.newCall(request).execute()) { + private T executePost(final Request request, final Class responseType) { + + final Response response; + final String responseBody; + try { + response = client.newCall(request).execute(); + responseBody = response.body().string(); + } catch (final IOException e) { + throw new EnclaveException("Failed to contact Enclave", e); + } + + try { if (response.isSuccessful()) { - return objectMapper.readValue(response.body().string(), responseType); + return objectMapper.readValue(responseBody, responseType); } else { final ErrorResponse errorResponse = - objectMapper.readValue(response.body().string(), ErrorResponse.class); + objectMapper.readValue(responseBody, ErrorResponse.class); throw new EnclaveException(errorResponse.getError()); } - } catch (Exception e) { - LOG.error("Enclave failed to execute {}", request, e); - throw e; + } catch (final JsonParseException | JsonMappingException e) { + throw new EnclaveException("Failed to deserialise received json", e); + } catch (final IOException e) { + throw new EnclaveException("Decoding json stream failed.", e); } } } diff --git a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java index 830e642dbe..d6ba4ecf3f 100644 --- a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java +++ b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java @@ -20,6 +20,7 @@ import static org.mockito.Mockito.when; import tech.pegasys.pantheon.enclave.Enclave; +import tech.pegasys.pantheon.enclave.EnclaveException; import tech.pegasys.pantheon.enclave.types.ReceiveRequest; import tech.pegasys.pantheon.enclave.types.ReceiveResponse; import tech.pegasys.pantheon.ethereum.chain.Blockchain; @@ -40,7 +41,6 @@ import tech.pegasys.pantheon.util.bytes.Bytes32; import tech.pegasys.pantheon.util.bytes.BytesValue; -import java.io.IOException; import java.util.Base64; import java.util.Optional; @@ -76,7 +76,7 @@ public class PrivacyPrecompiledContractTest { + "64") .extractArray()); - private Enclave mockEnclave() throws Exception { + private Enclave mockEnclave() { Enclave mockEnclave = mock(Enclave.class); ReceiveResponse response = new ReceiveResponse(VALID_PRIVATE_TRANSACTION_RLP_BASE64, ""); when(mockEnclave.receive(any(ReceiveRequest.class))).thenReturn(response); @@ -105,14 +105,14 @@ private PrivateTransactionProcessor mockPrivateTxProcessor() { return mockPrivateTransactionProcessor; } - private Enclave brokenMockEnclave() throws Exception { + private Enclave brokenMockEnclave() { Enclave mockEnclave = mock(Enclave.class); - when(mockEnclave.receive(any(ReceiveRequest.class))).thenThrow(IOException.class); + when(mockEnclave.receive(any(ReceiveRequest.class))).thenThrow(EnclaveException.class); return mockEnclave; } @Before - public void setUp() throws Exception { + public void setUp() { WorldStateArchive worldStateArchive; worldStateArchive = mock(WorldStateArchive.class); MutableWorldState mutableWorldState = mock(MutableWorldState.class); diff --git a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionHandlerTest.java b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionHandlerTest.java index f4bc7c0537..55b435cc47 100644 --- a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionHandlerTest.java +++ b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionHandlerTest.java @@ -13,6 +13,7 @@ package tech.pegasys.pantheon.ethereum.privacy; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -23,6 +24,7 @@ import tech.pegasys.pantheon.crypto.SECP256K1; import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; import tech.pegasys.pantheon.enclave.Enclave; +import tech.pegasys.pantheon.enclave.EnclaveException; import tech.pegasys.pantheon.enclave.types.ReceiveRequest; import tech.pegasys.pantheon.enclave.types.ReceiveResponse; import tech.pegasys.pantheon.enclave.types.SendRequest; @@ -40,7 +42,6 @@ import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.bytes.BytesValues; -import java.io.IOException; import java.math.BigInteger; import java.util.Optional; @@ -76,7 +77,7 @@ public class PrivateTransactionHandlerTest { .chainId(BigInteger.valueOf(2018)) .signAndBuild(KEY_PAIR); - Enclave mockEnclave() throws Exception { + Enclave mockEnclave() { Enclave mockEnclave = mock(Enclave.class); SendResponse response = new SendResponse(TRANSACTION_KEY); ReceiveResponse receiveResponse = new ReceiveResponse(new byte[0], "mock"); @@ -85,9 +86,9 @@ Enclave mockEnclave() throws Exception { return mockEnclave; } - Enclave brokenMockEnclave() throws Exception { + Enclave brokenMockEnclave() { Enclave mockEnclave = mock(Enclave.class); - when(mockEnclave.send(any(SendRequest.class))).thenThrow(IOException.class); + when(mockEnclave.send(any(SendRequest.class))).thenThrow(EnclaveException.class); return mockEnclave; } @@ -178,9 +179,11 @@ public void validPantheonTransactionThroughHandler() throws Exception { assertThat(markerTransaction.getValue()).isEqualTo(PUBLIC_TRANSACTION.getValue()); } - @Test(expected = IOException.class) - public void enclaveIsDownWhileHandling() throws Exception { - brokenPrivateTransactionHandler.sendToOrion(buildLegacyPrivateTransaction()); + @Test + public void enclaveIsDownWhileHandling() { + assertThatExceptionOfType(EnclaveException.class) + .isThrownBy( + () -> brokenPrivateTransactionHandler.sendToOrion(buildLegacyPrivateTransaction())); } @Test diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java index 77ab4b1370..9e61c3f154 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java @@ -94,7 +94,9 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.permissioning.PermReloadPermissionsFromFile; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.permissioning.PermRemoveAccountsFromWhitelist; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.permissioning.PermRemoveNodesFromWhitelist; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.privacy.eea.EeaGetTransactionCount; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.privacy.eea.EeaGetTransactionReceipt; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.privacy.eea.EeaPrivateNonceProvider; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.privacy.eea.EeaSendRawTransaction; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.privacy.priv.PrivCreatePrivacyGroup; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.privacy.priv.PrivDeletePrivacyGroup; @@ -345,7 +347,9 @@ blockchainQueries, new TransactionTracer(blockReplay), parameter), addMethods( enabledMethods, new EeaGetTransactionReceipt(blockchainQueries, enclave, parameter, privacyParameters), - new EeaSendRawTransaction(privateTransactionHandler, transactionPool, parameter)); + new EeaSendRawTransaction(privateTransactionHandler, transactionPool, parameter), + new EeaGetTransactionCount( + parameter, new EeaPrivateNonceProvider(enclave, privateTransactionHandler))); } if (priv) { addMethods( diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/RpcMethod.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/RpcMethod.java index 0e126427dd..f84ad42206 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/RpcMethod.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/RpcMethod.java @@ -41,6 +41,7 @@ public enum RpcMethod { PRIV_DELETE_PRIVACY_GROUP("priv_deletePrivacyGroup"), PRIV_FIND_PRIVACY_GROUP("priv_findPrivacyGroup"), EEA_SEND_RAW_TRANSACTION("eea_sendRawTransaction"), + EEA_GET_TRANSACTION_COUNT("eea_getTransactionCount"), ETH_ACCOUNTS("eth_accounts"), ETH_BLOCK_NUMBER("eth_blockNumber"), ETH_CALL("eth_call"), diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/eea/EeaGetTransactionCount.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/eea/EeaGetTransactionCount.java new file mode 100644 index 0000000000..6412c22038 --- /dev/null +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/eea/EeaGetTransactionCount.java @@ -0,0 +1,67 @@ +/* + * 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.eea; + +import static org.apache.logging.log4j.LogManager.getLogger; + +import tech.pegasys.pantheon.ethereum.core.Address; +import tech.pegasys.pantheon.ethereum.jsonrpc.RpcMethod; +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.response.JsonRpcError; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcErrorResponse; +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.Quantity; + +import org.apache.logging.log4j.Logger; + +public class EeaGetTransactionCount implements JsonRpcMethod { + + private static final Logger LOG = getLogger(); + + private final JsonRpcParameter parameters; + private final EeaPrivateNonceProvider nonceProvider; + + public EeaGetTransactionCount( + final JsonRpcParameter parameters, final EeaPrivateNonceProvider nonceProvider) { + this.parameters = parameters; + this.nonceProvider = nonceProvider; + } + + @Override + public String getName() { + return RpcMethod.EEA_GET_TRANSACTION_COUNT.getMethodName(); + } + + @Override + public JsonRpcResponse response(final JsonRpcRequest request) { + if (request.getParamLength() != 3) { + return new JsonRpcErrorResponse(request.getId(), JsonRpcError.INVALID_PARAMS); + } + + final Address address = parameters.required(request.getParams(), 0, Address.class); + final String privateFrom = parameters.required(request.getParams(), 1, String.class); + final String[] privateFor = parameters.required(request.getParams(), 2, String[].class); + + try { + final long nonce = nonceProvider.determineNonce(privateFrom, privateFor, address); + return new JsonRpcSuccessResponse(request.getId(), Quantity.create(nonce)); + } catch (final Exception e) { + LOG.error(e.getMessage(), e); + return new JsonRpcErrorResponse( + request.getId(), JsonRpcError.GET_PRIVATE_TRANSACTION_NONCE_ERROR); + } + } +} diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/eea/EeaPrivateNonceProvider.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/eea/EeaPrivateNonceProvider.java new file mode 100644 index 0000000000..33c0378f5e --- /dev/null +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/eea/EeaPrivateNonceProvider.java @@ -0,0 +1,62 @@ +/* + * 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.eea; + +import tech.pegasys.pantheon.enclave.Enclave; +import tech.pegasys.pantheon.enclave.types.FindPrivacyGroupRequest; +import tech.pegasys.pantheon.enclave.types.PrivacyGroup; +import tech.pegasys.pantheon.enclave.types.PrivacyGroup.Type; +import tech.pegasys.pantheon.ethereum.core.Address; +import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionHandler; + +import java.util.List; +import java.util.stream.Collectors; + +import com.google.common.collect.Lists; +import org.bouncycastle.util.Arrays; + +public class EeaPrivateNonceProvider { + + private final Enclave enclave; + private final PrivateTransactionHandler privateTransactionHandler; + + public EeaPrivateNonceProvider( + final Enclave enclave, final PrivateTransactionHandler privateTransactionHandler) { + this.enclave = enclave; + this.privateTransactionHandler = privateTransactionHandler; + } + + public long determineNonce( + final String privateFrom, final String[] privateFor, final Address address) { + + final String[] groupMembers = Arrays.append(privateFor, privateFrom); + + final FindPrivacyGroupRequest request = new FindPrivacyGroupRequest(groupMembers); + final List matchingGroups = Lists.newArrayList(enclave.findPrivacyGroup(request)); + + final List legacyGroups = + matchingGroups.stream() + .filter(group -> group.getType() == Type.LEGACY) + .collect(Collectors.toList()); + + if (legacyGroups.size() != 1) { + throw new RuntimeException( + String.format( + "Found invalid number of privacy groups (%d), expected 1.", legacyGroups.size())); + } + + final String privacyGroupId = legacyGroups.get(0).getPrivacyGroupId(); + + return privateTransactionHandler.getSenderNonce(address, privacyGroupId); + } +} diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java index 0c19e0cdb0..3e66fb6503 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java @@ -114,6 +114,7 @@ public enum JsonRpcError { FIND_PRIVACY_GROUP_ERROR(-50100, "Error finding privacy group"), VALUE_NOT_ZERO(-50100, "We cannot transfer ether in private transaction yet."), DECODE_ERROR(-50100, "Unable to decode the private signed raw transaction"), + GET_PRIVATE_TRANSACTION_NONCE_ERROR(-50100, "Unable to determine nonce for account in group."), CANT_CONNECT_TO_LOCAL_PEER(-32100, "Cannot add local node as peer."), diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/eea/EeaGetTransactionCountTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/eea/EeaGetTransactionCountTest.java new file mode 100644 index 0000000000..baf687f062 --- /dev/null +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/eea/EeaGetTransactionCountTest.java @@ -0,0 +1,92 @@ +/* + * 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.eea; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import tech.pegasys.pantheon.ethereum.core.Address; +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; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcErrorResponse; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse; + +import org.junit.Before; +import org.junit.Test; + +public class EeaGetTransactionCountTest { + + private EeaPrivateNonceProvider nonceProvider = mock(EeaPrivateNonceProvider.class); + private JsonRpcRequest request; + + private final String privateFrom = "thePrivateFromKey"; + private final String[] privateFor = new String[] {"first", "second", "third"}; + private final Address address = Address.fromHexString("55"); + + @Before + public void setup() { + final Object[] jsonBody = new Object[] {address.toString(), privateFrom, privateFor}; + request = new JsonRpcRequest("2.0", "eea_getTransactionCount", jsonBody); + } + + @Test + public void validRequestProducesExpectedNonce() { + final long reportedNonce = 8L; + final EeaGetTransactionCount method = + new EeaGetTransactionCount(new JsonRpcParameter(), nonceProvider); + + when(nonceProvider.determineNonce(privateFrom, privateFor, address)).thenReturn(reportedNonce); + + final JsonRpcResponse response = method.response(request); + assertThat(response).isInstanceOf(JsonRpcSuccessResponse.class); + + final JsonRpcSuccessResponse successResponse = (JsonRpcSuccessResponse) response; + int returnedValue = Integer.decode((String) successResponse.getResult()); + assertThat(returnedValue).isEqualTo(reportedNonce); + } + + @Test + public void nonceProviderThrowsRuntimeExceptionProducesErrorResponse() { + final EeaGetTransactionCount method = + new EeaGetTransactionCount(new JsonRpcParameter(), nonceProvider); + + when(nonceProvider.determineNonce(privateFrom, privateFor, address)) + .thenThrow(RuntimeException.class); + + final JsonRpcResponse response = method.response(request); + assertThat(response).isInstanceOf(JsonRpcErrorResponse.class); + + final JsonRpcErrorResponse errorResponse = (JsonRpcErrorResponse) response; + assertThat(errorResponse.getError()) + .isEqualTo(JsonRpcError.GET_PRIVATE_TRANSACTION_NONCE_ERROR); + } + + @Test + public void nonceProviderThrowsAnExceptionProducesErrorResponse() { + final EeaGetTransactionCount method = + new EeaGetTransactionCount(new JsonRpcParameter(), nonceProvider); + + when(nonceProvider.determineNonce(privateFrom, privateFor, address)) + .thenThrow(RuntimeException.class); + + final JsonRpcResponse response = method.response(request); + assertThat(response).isInstanceOf(JsonRpcErrorResponse.class); + + final JsonRpcErrorResponse errorResponse = (JsonRpcErrorResponse) response; + assertThat(errorResponse.getError()) + .isEqualTo(JsonRpcError.GET_PRIVATE_TRANSACTION_NONCE_ERROR); + } +} diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/eea/EeaPrivateNonceProviderTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/eea/EeaPrivateNonceProviderTest.java new file mode 100644 index 0000000000..dda17b1bee --- /dev/null +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/eea/EeaPrivateNonceProviderTest.java @@ -0,0 +1,104 @@ +/* + * 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.eea; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import tech.pegasys.pantheon.enclave.Enclave; +import tech.pegasys.pantheon.enclave.EnclaveException; +import tech.pegasys.pantheon.enclave.types.FindPrivacyGroupRequest; +import tech.pegasys.pantheon.enclave.types.PrivacyGroup; +import tech.pegasys.pantheon.enclave.types.PrivacyGroup.Type; +import tech.pegasys.pantheon.ethereum.core.Address; +import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionHandler; + +import com.google.common.collect.Lists; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +public class EeaPrivateNonceProviderTest { + + private final Address address = Address.fromHexString("55"); + private Enclave enclave = mock(Enclave.class); + private PrivateTransactionHandler privateTransactionHandler = + mock(PrivateTransactionHandler.class); + + private final EeaPrivateNonceProvider nonceProvider = + new EeaPrivateNonceProvider(enclave, privateTransactionHandler); + + @Test + public void validRequestProducesExpectedNonce() { + final long reportedNonce = 8L; + PrivacyGroup[] returnedGroups = + new PrivacyGroup[] { + new PrivacyGroup("Group1", Type.LEGACY, "Group1_Name", "Group1_Desc", new String[0]), + }; + + final ArgumentCaptor groupMembersCaptor = + ArgumentCaptor.forClass(FindPrivacyGroupRequest.class); + + when(enclave.findPrivacyGroup(groupMembersCaptor.capture())).thenReturn(returnedGroups); + when(privateTransactionHandler.getSenderNonce(address, "Group1")).thenReturn(reportedNonce); + + final long nonce = + nonceProvider.determineNonce("privateFrom", new String[] {"first", "second"}, address); + + assertThat(nonce).isEqualTo(reportedNonce); + assertThat(groupMembersCaptor.getValue().addresses()) + .containsAll(Lists.newArrayList("privateFrom", "first", "second")); + } + + @Test + public void moreThanOneMatchingLegacyGroupThrowsException() { + PrivacyGroup[] returnedGroups = + new PrivacyGroup[] { + new PrivacyGroup("Group1", Type.LEGACY, "Group1_Name", "Group1_Desc", new String[0]), + new PrivacyGroup("Group2", Type.LEGACY, "Group2_Name", "Group2_Desc", new String[0]), + }; + + when(enclave.findPrivacyGroup(any())).thenReturn(returnedGroups); + + assertThatExceptionOfType(RuntimeException.class) + .isThrownBy( + () -> + nonceProvider.determineNonce( + "privateFrom", new String[] {"first", "second"}, address)); + } + + @Test + public void noMatchingLegacyGroupsReturnsError() { + PrivacyGroup[] returnedGroups = new PrivacyGroup[] {}; + + when(enclave.findPrivacyGroup(any())).thenReturn(returnedGroups); + + assertThatExceptionOfType(RuntimeException.class) + .isThrownBy( + () -> + nonceProvider.determineNonce( + "privateFrom", new String[] {"first", "second"}, address)); + } + + @Test + public void enclaveThrowingAnExceptionResultsInErrorResponse() { + when(enclave.findPrivacyGroup(any())).thenThrow(new EnclaveException("Enclave Failed")); + assertThatExceptionOfType(EnclaveException.class) + .isThrownBy( + () -> + nonceProvider.determineNonce( + "privateFrom", new String[] {"first", "second"}, address)); + } +}