Skip to content

Commit

Permalink
Added eea_getTransactionCount Json Rpc (PegaSysEng#1861)
Browse files Browse the repository at this point in the history
Exposers a Json Rpc which allows "legacy" users to query for the
nonce of a given address, in a group of private users.
  • Loading branch information
rain-on authored and pscott committed Sep 2, 2019
1 parent ddd6b3a commit 5e61d99
Show file tree
Hide file tree
Showing 10 changed files with 380 additions and 28 deletions.
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);
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 @@ -346,7 +348,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 @@ -42,6 +42,7 @@ public enum RpcMethod {
PRIV_FIND_PRIVACY_GROUP("priv_findPrivacyGroup"),
PRIV_GET_CODE("priv_getCode"),
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,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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<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) {
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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."),

Expand Down
Loading

0 comments on commit 5e61d99

Please sign in to comment.