Skip to content

Commit

Permalink
Stop processing blocks when enclave is down (hyperledger#253)
Browse files Browse the repository at this point in the history
only when you are trying to process a privacy marker transaction
Wrap errors with Enclave exception

Signed-off-by: Antony Denyer <email@antonydenyer.co.uk>

Co-authored-by: Lucas Saldanha <lucascrsaldanha@gmail.com>
  • Loading branch information
antonydenyer and lucassaldanha committed Dec 19, 2019
1 parent 74e352c commit f2a3c00
Show file tree
Hide file tree
Showing 14 changed files with 179 additions and 98 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@

import org.hyperledger.besu.controller.KeyPairUtil;
import org.hyperledger.besu.enclave.Enclave;
import org.hyperledger.besu.enclave.EnclaveException;
import org.hyperledger.besu.enclave.EnclaveClientException;
import org.hyperledger.besu.enclave.EnclaveFactory;
import org.hyperledger.besu.enclave.EnclaveIOException;
import org.hyperledger.besu.enclave.EnclaveServerException;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.privacy.storage.PrivacyStorageProvider;
Expand Down Expand Up @@ -119,8 +121,10 @@ public void testOrionConnection(final List<PrivacyNode> otherNodes) {
try {
enclaveClient.send(payload, orion.getDefaultPublicKey(), to);
return true;
} catch (final EnclaveException e) {
LOG.info("Waiting for enclave connectivity");
} catch (final EnclaveClientException
| EnclaveIOException
| EnclaveServerException e) {
LOG.warn("Waiting for enclave connectivity");
return false;
}
});
Expand Down
46 changes: 30 additions & 16 deletions enclave/src/main/java/org/hyperledger/besu/enclave/Enclave.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@
import java.nio.charset.StandardCharsets;
import java.util.List;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Enclave {
Expand Down Expand Up @@ -145,34 +143,50 @@ private <T> T post(
try {
bodyText = objectMapper.writeValueAsString(content);
} catch (final JsonProcessingException e) {
throw new EnclaveException("Unable to serialise object to json representation.");
throw new EnclaveClientException(400, "Unable to serialise request.");
}

return requestTransmitter.post(mediaType, bodyText, endpoint, responseBodyHandler);
}

private <T> T handleJsonResponse(
final int statusCode, final byte[] body, final Class<T> responseType) {

if (isSuccess(statusCode)) {
return parseResponse(statusCode, body, responseType);
} else if (clientError(statusCode)) {
final ErrorResponse errorResponse = parseResponse(statusCode, body, ErrorResponse.class);
throw new EnclaveClientException(statusCode, errorResponse.getError());
} else {
final ErrorResponse errorResponse = parseResponse(statusCode, body, ErrorResponse.class);
throw new EnclaveServerException(statusCode, errorResponse.getError());
}
}

private <T> T parseResponse(
final int statusCode, final byte[] body, final Class<T> responseType) {
try {
if (statusCode == 200) {
return objectMapper.readValue(body, responseType);
} else {
final ErrorResponse errorResponse = objectMapper.readValue(body, ErrorResponse.class);
throw new EnclaveException(errorResponse.getError());
}
} 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);
return objectMapper.readValue(body, responseType);
} catch (IOException e) {
final String utf8EncodedBody = new String(body, StandardCharsets.UTF_8);
throw new EnclaveClientException(statusCode, utf8EncodedBody);
}
}

private boolean clientError(final int statusCode) {
return statusCode >= 400 && statusCode < 500;
}

private boolean isSuccess(final int statusCode) {
return statusCode == 200;
}

private String handleRawResponse(final int statusCode, final byte[] body) {
final String bodyText = new String(body, StandardCharsets.UTF_8);
if (statusCode == 200) {
if (isSuccess(statusCode)) {
return bodyText;
}
throw new EnclaveException(
String.format("Request failed with %d; body={%s}", statusCode, bodyText));
throw new EnclaveClientException(
statusCode, String.format("Request failed with %d; body={%s}", statusCode, bodyText));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.enclave;

public class EnclaveClientException extends IllegalArgumentException {
private int statusCode;

public EnclaveClientException(final int statusCode, final String message) {
super(message);
this.statusCode = statusCode;
}

public EnclaveClientException(final String message, final Throwable cause) {
super(message, cause);
}

public int getStatusCode() {
return statusCode;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public EnclaveFactory(final Vertx vertx) {

public Enclave createVertxEnclave(final URI enclaveUri) {
if (enclaveUri.getPort() == -1) {
throw new EnclaveException("Illegal URI - no port specified");
throw new EnclaveIOException("Illegal URI - no port specified");
}

final HttpClientOptions clientOptions = new HttpClientOptions();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
*/
package org.hyperledger.besu.enclave;

public class EnclaveException extends IllegalArgumentException {
public EnclaveException(final String message) {
super(message);
public class EnclaveIOException extends RuntimeException {
public EnclaveIOException(final String message, final Throwable cause) {
super(message, cause);
}

public EnclaveException(final String message, final Throwable cause) {
super(message, cause);
public EnclaveIOException(final String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.enclave;

public class EnclaveServerException extends RuntimeException {
private int statusCode;

public EnclaveServerException(final int statusCode, final String message) {
super(message);
this.statusCode = statusCode;
}

public int getStatusCode() {
return statusCode;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,13 @@ protected <T> T sendRequest(
request.end();
}
return result.get();
} catch (final ExecutionException e) {
if (e.getCause() instanceof EnclaveException) {
throw (EnclaveException) e.getCause();
} catch (final ExecutionException | InterruptedException e) {
if (e.getCause() instanceof EnclaveClientException) {
throw (EnclaveClientException) e.getCause();
} else if (e.getCause() instanceof EnclaveServerException) {
throw (EnclaveServerException) e.getCause();
}
throw new EnclaveException("Failure during reception", e);
} catch (final InterruptedException e) {
throw new EnclaveException("Task interrupted while waiting for Enclave response.", e);
} catch (final Exception e) {
throw new EnclaveException("Other error", e);
throw new EnclaveIOException("Enclave Communication Failed", e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import static org.apache.logging.log4j.LogManager.getLogger;

import org.hyperledger.besu.enclave.EnclaveException;
import org.hyperledger.besu.enclave.EnclaveClientException;
import org.hyperledger.besu.enclave.types.ReceiveResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcEnclaveErrorConverter;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
Expand Down Expand Up @@ -101,7 +101,7 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) {

privateTransaction = PrivateTransaction.readFrom(bytesValueRLPInput);
privacyGroupId = receiveResponse.getPrivacyGroupId();
} catch (final EnclaveException e) {
} catch (final EnclaveClientException e) {
return handleEnclaveException(requestContext, e);
}

Expand Down Expand Up @@ -172,7 +172,7 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) {
}

private JsonRpcResponse handleEnclaveException(
final JsonRpcRequestContext requestContext, final EnclaveException e) {
final JsonRpcRequestContext requestContext, final EnclaveClientException e) {
final JsonRpcError jsonRpcError =
JsonRpcEnclaveErrorConverter.convertEnclaveInvalidReason(e.getMessage());
switch (jsonRpcError) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import static org.mockito.Mockito.when;

import org.hyperledger.besu.crypto.SECP256K1;
import org.hyperledger.besu.enclave.EnclaveException;
import org.hyperledger.besu.enclave.EnclaveServerException;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
Expand Down Expand Up @@ -283,7 +283,7 @@ public void invalidTransactionWithoutPrivateFromFieldFailsWithDecodeError() {
@Test
public void invalidTransactionIsNotSentToTransactionPool() {
when(privacyController.sendTransaction(any(PrivateTransaction.class)))
.thenThrow(new EnclaveException("enclave failed to execute"));
.thenThrow(new EnclaveServerException(500, "enclave failed to execute"));

final JsonRpcRequestContext request =
new JsonRpcRequestContext(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import static org.mockito.Mockito.when;

import org.hyperledger.besu.enclave.Enclave;
import org.hyperledger.besu.enclave.EnclaveException;
import org.hyperledger.besu.enclave.EnclaveServerException;
import org.hyperledger.besu.enclave.types.PrivacyGroup;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
Expand Down Expand Up @@ -242,7 +242,7 @@ public void returnsCorrectExceptionMissingParam() {
@Test
public void returnsCorrectErrorEnclaveError() {
when(privacyController.createPrivacyGroup(ADDRESSES, NAME, DESCRIPTION))
.thenThrow(new EnclaveException(""));
.thenThrow(new EnclaveServerException(500, ""));
final PrivCreatePrivacyGroup privCreatePrivacyGroup =
new PrivCreatePrivacyGroup(privacyController);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
import static org.mockito.Mockito.when;

import org.hyperledger.besu.crypto.SECP256K1;
import org.hyperledger.besu.enclave.Enclave;
import org.hyperledger.besu.enclave.EnclaveException;
import org.hyperledger.besu.enclave.EnclaveClientException;
import org.hyperledger.besu.enclave.EnclaveServerException;
import org.hyperledger.besu.enclave.types.ReceiveResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
Expand Down Expand Up @@ -139,10 +139,8 @@ public class PrivGetTransactionReceiptTest {

private final BlockchainQueries blockchainQueries = mock(BlockchainQueries.class);
private final Blockchain blockchain = mock(Blockchain.class);
private final Enclave enclave = mock(Enclave.class);
private final PrivacyParameters privacyParameters = mock(PrivacyParameters.class);
private final PrivateStateStorage privateStateStorage = mock(PrivateStateStorage.class);
private final PrivacyController failingPrivacyController = mock(PrivacyController.class);
private final PrivacyController privacyController = mock(PrivacyController.class);

@Before
Expand Down Expand Up @@ -174,7 +172,6 @@ public void setUp() {

@Test
public void returnReceiptIfTransactionExists() {
when(privacyParameters.getEnclave()).thenReturn(enclave);

final PrivGetTransactionReceipt privGetTransactionReceipt =
new PrivGetTransactionReceipt(blockchainQueries, privacyParameters, privacyController);
Expand All @@ -192,12 +189,11 @@ public void returnReceiptIfTransactionExists() {

@Test
public void enclavePayloadNotFoundResultsInSuccessButNullResponse() {
when(failingPrivacyController.retrieveTransaction(anyString()))
.thenThrow(new EnclaveException("EnclavePayloadNotFound"));
when(privacyController.retrieveTransaction(anyString()))
.thenThrow(new EnclaveClientException(404, "EnclavePayloadNotFound"));

final PrivGetTransactionReceipt privGetTransactionReceipt =
new PrivGetTransactionReceipt(
blockchainQueries, privacyParameters, failingPrivacyController);
new PrivGetTransactionReceipt(blockchainQueries, privacyParameters, privacyController);
final Object[] params = new Object[] {transaction.getHash()};
final JsonRpcRequestContext request =
new JsonRpcRequestContext(new JsonRpcRequest("1", "priv_getTransactionReceipt", params));
Expand All @@ -213,7 +209,6 @@ public void enclavePayloadNotFoundResultsInSuccessButNullResponse() {
@Test
public void markerTransactionNotAvailableResultsInNullResponse() {
when(blockchain.getTransactionLocation(nullable(Hash.class))).thenReturn(Optional.empty());
when(privacyParameters.getEnclave()).thenReturn(enclave);

final PrivGetTransactionReceipt privGetTransactionReceipt =
new PrivGetTransactionReceipt(blockchainQueries, privacyParameters, privacyController);
Expand All @@ -231,11 +226,10 @@ public void markerTransactionNotAvailableResultsInNullResponse() {

@Test
public void enclaveConnectionIssueThrowsRuntimeException() {
when(failingPrivacyController.retrieveTransaction(anyString()))
.thenThrow(EnclaveException.class);
when(privacyController.retrieveTransaction(anyString()))
.thenThrow(EnclaveServerException.class);
final PrivGetTransactionReceipt privGetTransactionReceipt =
new PrivGetTransactionReceipt(
blockchainQueries, privacyParameters, failingPrivacyController);
new PrivGetTransactionReceipt(blockchainQueries, privacyParameters, privacyController);
final Object[] params = new Object[] {transaction.getHash()};
final JsonRpcRequestContext request =
new JsonRpcRequestContext(new JsonRpcRequest("1", "priv_getTransactionReceipt", params));
Expand Down Expand Up @@ -266,9 +260,8 @@ public void transactionReceiptContainsRevertReasonWhenInvalidTransactionOccurs()
@Test
public void enclaveKeysCannotDecryptPayloadThrowsRuntimeException() {
final String keysCannotDecryptPayloadMsg = "EnclaveKeysCannotDecryptPayload";
when(privacyParameters.getEnclave()).thenReturn(enclave);
when(privacyController.retrieveTransaction(any()))
.thenThrow(new EnclaveException(keysCannotDecryptPayloadMsg));
.thenThrow(new EnclaveClientException(400, keysCannotDecryptPayloadMsg));

final PrivGetTransactionReceipt privGetTransactionReceipt =
new PrivGetTransactionReceipt(blockchainQueries, privacyParameters, privacyController);
Expand All @@ -277,7 +270,7 @@ public void enclaveKeysCannotDecryptPayloadThrowsRuntimeException() {
new JsonRpcRequestContext(new JsonRpcRequest("1", "priv_getTransactionReceipt", params));

final Throwable t = catchThrowable(() -> privGetTransactionReceipt.response(request));
assertThat(t).isInstanceOf(EnclaveException.class);
assertThat(t).isInstanceOf(EnclaveClientException.class);
assertThat(t.getMessage()).isEqualTo(keysCannotDecryptPayloadMsg);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
import static org.hyperledger.besu.crypto.Hash.keccak256;

import org.hyperledger.besu.enclave.Enclave;
import org.hyperledger.besu.enclave.EnclaveClientException;
import org.hyperledger.besu.enclave.EnclaveIOException;
import org.hyperledger.besu.enclave.EnclaveServerException;
import org.hyperledger.besu.enclave.types.ReceiveResponse;
import org.hyperledger.besu.ethereum.core.Gas;
import org.hyperledger.besu.ethereum.core.Hash;
Expand Down Expand Up @@ -87,12 +90,19 @@ public Gas gasRequirement(final BytesValue input) {
@Override
public BytesValue compute(final BytesValue input, final MessageFrame messageFrame) {
final String key = BytesValues.asBase64String(input);

final ReceiveResponse receiveResponse;
try {
receiveResponse = enclave.receive(key);
} catch (final Exception e) {
LOG.debug("Enclave probably does not have private transaction payload with key {}", key, e);
} catch (final EnclaveClientException e) {
LOG.debug("Can not fetch private transaction payload with key {}", key, e);
return BytesValue.EMPTY;
} catch (final EnclaveServerException e) {
LOG.error("Enclave is responding but errored perhaps it has a misconfiguration?", e);
throw e;
} catch (final EnclaveIOException e) {
LOG.error("Can not communicate with enclave is it up?", e);
throw e;
}

final BytesValueRLPInput bytesValueRLPInput =
Expand Down
Loading

0 comments on commit f2a3c00

Please sign in to comment.