Skip to content
This repository has been archived by the owner on Sep 26, 2019. It is now read-only.

Added eea_getTransactionCount Json Rpc #1861

Merged
merged 15 commits into from
Aug 26, 2019
48 changes: 33 additions & 15 deletions enclave/src/main/java/tech/pegasys/pantheon/enclave/Enclave.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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> T executePost(final Request request, final Class<T> responseType) throws Exception {
try (Response response = client.newCall(request).execute()) {
private <T> T executePost(final Request request, final Class<T> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -105,14 +105,14 @@ private PrivateTransactionProcessor mockPrivateTxProcessor() {
return mockPrivateTransactionProcessor;
}

private Enclave brokenMockEnclave() throws Exception {
private Enclave brokenMockEnclave() {
Enclave mockEnclave = mock(Enclave.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

throws Exception shouldn't be needed now it's throwing the runtime EnclaveException

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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");
Expand All @@ -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;
}

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* 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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think this class should be name PrivGetLegacyTransactionCount

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talked to Madeline last night - given this capability is really required of Legacy Privacy - it belongs in the Eea namespace, and thus, the spec will need updating.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More to the point - priv_ namespace is geared toward the new Privacy Group - so having "priv" and "legacy" in a name is not a valid combination.


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.FIND_PRIVACY_GROUP_ERROR);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks like the wrong error type

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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.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.apache.logging.log4j.Logger;
import org.bouncycastle.util.Arrays;

public class EeaPrivateNonceProvider {

private static final Logger LOG = getLogger();

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<PrivacyGroup> matchingGroups = Lists.newArrayList(enclave.findPrivacyGroup(request));

final List<PrivacyGroup> legacyGroups =
matchingGroups.stream()
.filter(group -> group.getType() == Type.LEGACY)
.collect(Collectors.toList());

if (legacyGroups.size() != 1) {
final String errorMessage =
String.format(
"Found invalid number of privacy groups (%d), expected 1.", legacyGroups.size());
LOG.error(errorMessage);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to log and throw, perhaps just throw with the error message. Will be logged in rpc layer

throw new RuntimeException(errorMessage);
}

final String privacyGroupId = legacyGroups.get(0).getPrivacyGroupId();

return privateTransactionHandler.getSenderNonce(address, privacyGroupId);
}
}
Loading