Privacy multitenancy validation (#296)

Signed-off-by: Jason Frame <jasonwframe@gmail.com>
pull/302/head
Jason Frame 5 years ago committed by GitHub
parent ef61b54bba
commit a7cde4dd8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
  2. 29
      besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java
  3. 3
      ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivGetPrivateTransactionIntegrationTest.java
  4. 3
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/MultiTenancyRpcMethodDecorator.java
  5. 15
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransaction.java
  6. 11
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDeletePrivacyGroup.java
  7. 9
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDistributeRawTransaction.java
  8. 11
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivFindPrivacyGroup.java
  9. 10
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetEeaTransactionCount.java
  10. 5
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetPrivateTransaction.java
  11. 30
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionCount.java
  12. 4
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionReceipt.java
  13. 47
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivacyApiGroupJsonRpcMethods.java
  14. 7
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/MultiTenancyRpcMethodDecoratorTest.java
  15. 29
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransactionTest.java
  16. 4
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCreatePrivacyGroupTest.java
  17. 24
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDeletePrivacyGroupTest.java
  18. 24
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDistributeRawTransactionTest.java
  19. 19
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivFindPrivacyGroupTest.java
  20. 31
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetEeaTransactionCountTest.java
  21. 34
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetPrivateTransactionTest.java
  22. 49
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionCountTest.java
  23. 14
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivacyApiGroupJsonRpcMethodsTest.java
  24. 230
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/DefaultPrivacyController.java
  25. 133
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyController.java
  26. 22
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyValidationException.java
  27. 203
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivacyController.java
  28. 20
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/DefaultPrivacyControllerTest.java
  29. 313
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyControllerTest.java

@ -660,8 +660,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
@Option(
names = {"--privacy-multi-tenancy-enabled"},
description = "Enable multi-tenant private transactions (default: ${DEFAULT-VALUE})",
hidden = true)
description = "Enable multi-tenant private transactions (default: ${DEFAULT-VALUE})")
private final Boolean isPrivacyMultiTenancyEnabled = false;
@Option(
@ -1393,7 +1392,9 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
privacyParametersBuilder.setEnabled(true);
privacyParametersBuilder.setEnclaveUrl(privacyUrl);
privacyParametersBuilder.setMultiTenancyEnabled(isPrivacyMultiTenancyEnabled);
if (privacyPublicKeyFile() != null) {
final boolean hasPrivacyPublicKey = privacyPublicKeyFile() != null;
if (hasPrivacyPublicKey && !isPrivacyMultiTenancyEnabled) {
try {
privacyParametersBuilder.setEnclavePublicKeyUsingFile(privacyPublicKeyFile());
} catch (final IOException e) {
@ -1403,7 +1404,10 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
throw new ParameterException(
commandLine, "Contents of privacy-public-key-file invalid: " + e.getMessage(), e);
}
} else {
} else if (hasPrivacyPublicKey) {
throw new ParameterException(
commandLine, "Privacy multi-tenancy and privacy public key cannot be used together");
} else if (!isPrivacyMultiTenancyEnabled) {
throw new ParameterException(
commandLine, "Please specify Enclave public key file path to enable privacy");
}

@ -2669,6 +2669,14 @@ public class BesuCommandTest extends CommandTestAbstract {
assertThat(commandErrorOutput.toString()).isEmpty();
}
@Test
public void privacyWithoutPrivacyPublicKeyFails() {
parseCommand("--privacy-enabled", "--privacy-url", ENCLAVE_URI);
assertThat(commandErrorOutput.toString())
.startsWith("Please specify Enclave public key file path to enable privacy");
}
@Test
public void mustVerifyPrivacyIsDisabled() {
parseCommand();
@ -2694,9 +2702,7 @@ public class BesuCommandTest extends CommandTestAbstract {
"--rpc-http-authentication-enabled",
"--privacy-multi-tenancy-enabled",
"--rpc-http-authentication-jwt-public-key-file",
"/non/existent/file",
"--privacy-public-key-file",
ENCLAVE_PUBLIC_KEY_PATH);
"/non/existent/file");
final ArgumentCaptor<PrivacyParameters> privacyParametersArgumentCaptor =
ArgumentCaptor.forClass(PrivacyParameters.class);
@ -2713,13 +2719,26 @@ public class BesuCommandTest extends CommandTestAbstract {
"--privacy-enabled",
"--privacy-multi-tenancy-enabled",
"--rpc-http-authentication-jwt-public-key-file",
"/non/existent/file");
assertThat(commandErrorOutput.toString())
.startsWith(
"Privacy multi-tenancy requires either http authentication to be enabled or WebSocket authentication to be enabled");
}
@Test
public void privacyMultiTenancyWithPrivacyPublicKeyFileFails() {
parseCommand(
"--privacy-enabled",
"--rpc-http-authentication-enabled",
"--privacy-multi-tenancy-enabled",
"--rpc-http-authentication-jwt-public-key-file",
"/non/existent/file",
"--privacy-public-key-file",
ENCLAVE_PUBLIC_KEY_PATH);
assertThat(commandErrorOutput.toString())
.startsWith(
"Privacy multi-tenancy requires either http authentication to be enabled or WebSocket authentication to be enabled");
.startsWith("Privacy multi-tenancy and privacy public key cannot be used together");
}
private Path createFakeGenesisFile(final JsonObject jsonGenesis) throws IOException {

@ -37,6 +37,7 @@ import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.privacy.DefaultPrivacyController;
import org.hyperledger.besu.ethereum.privacy.PrivacyController;
import org.hyperledger.besu.ethereum.privacy.PrivateTransaction;
import org.hyperledger.besu.ethereum.privacy.Restriction;
@ -91,7 +92,7 @@ public class PrivGetPrivateTransactionIntegrationTest {
final EnclaveFactory factory = new EnclaveFactory(vertx);
enclave = factory.createVertxEnclave(testHarness.clientUrl());
privacyController = new PrivacyController(enclave, null, null, null, null);
privacyController = new DefaultPrivacyController(enclave, null, null, null, null);
}
@AfterClass

@ -19,6 +19,7 @@ import static org.apache.logging.log4j.LogManager.getLogger;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcUnauthorizedResponse;
@ -49,7 +50,7 @@ public class MultiTenancyRpcMethodDecorator implements JsonRpcMethod {
return new JsonRpcUnauthorizedResponse(id, JsonRpcError.UNAUTHORIZED);
} else if (MultiTenancyUserUtil.enclavePublicKey(user).isEmpty()) {
LOG.error("Request token does not contain an enclave public key");
return new JsonRpcUnauthorizedResponse(id, JsonRpcError.UNAUTHORIZED);
return new JsonRpcErrorResponse(id, JsonRpcError.INVALID_REQUEST);
} else {
return rpcMethod.response(requestContext);
}

@ -14,7 +14,10 @@
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.eea;
import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcEnclaveErrorConverter;
import static org.apache.logging.log4j.LogManager.getLogger;
import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcEnclaveErrorConverter.convertEnclaveInvalidReason;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.ENCLAVE_ERROR;
import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcErrorConverter;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
@ -27,12 +30,16 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcRespon
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool;
import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException;
import org.hyperledger.besu.ethereum.privacy.PrivacyController;
import org.hyperledger.besu.ethereum.privacy.PrivateTransaction;
import org.hyperledger.besu.ethereum.privacy.SendTransactionResponse;
import org.apache.logging.log4j.Logger;
public class EeaSendRawTransaction implements JsonRpcMethod {
private static final Logger LOG = getLogger();
private final PrivacySendTransaction privacySendTransaction;
private final EnclavePublicKeyProvider enclavePublicKeyProvider;
private final TransactionPool transactionPool;
@ -68,10 +75,12 @@ public class EeaSendRawTransaction implements JsonRpcMethod {
sendTransactionResponse =
privacyController.sendTransaction(
privateTransaction, enclavePublicKeyProvider.getEnclaveKey(requestContext.getUser()));
} catch (final MultiTenancyValidationException e) {
LOG.error("Unauthorized privacy multi-tenancy rpc request. {}", e.getMessage());
return new JsonRpcErrorResponse(requestContext.getRequest().getId(), ENCLAVE_ERROR);
} catch (final Exception e) {
return new JsonRpcErrorResponse(
requestContext.getRequest().getId(),
JsonRpcEnclaveErrorConverter.convertEnclaveInvalidReason(e.getMessage()));
requestContext.getRequest().getId(), convertEnclaveInvalidReason(e.getMessage()));
}
return privacySendTransaction.validateAndExecute(

@ -15,15 +15,16 @@
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv;
import static org.apache.logging.log4j.LogManager.getLogger;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.DELETE_PRIVACY_GROUP_ERROR;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.EnclavePublicKeyProvider;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException;
import org.hyperledger.besu.ethereum.privacy.PrivacyController;
import org.apache.logging.log4j.Logger;
@ -57,10 +58,14 @@ public class PrivDeletePrivacyGroup implements JsonRpcMethod {
response =
privacyController.deletePrivacyGroup(
privacyGroupId, enclavePublicKeyProvider.getEnclaveKey(requestContext.getUser()));
} catch (final MultiTenancyValidationException e) {
LOG.error("Unauthorized privacy multi-tenancy rpc request. {}", e.getMessage());
return new JsonRpcErrorResponse(
requestContext.getRequest().getId(), DELETE_PRIVACY_GROUP_ERROR);
} catch (Exception e) {
LOG.error("Failed to fetch transaction", e);
LOG.error("Failed to delete privacy group", e);
return new JsonRpcErrorResponse(
requestContext.getRequest().getId(), JsonRpcError.DELETE_PRIVACY_GROUP_ERROR);
requestContext.getRequest().getId(), DELETE_PRIVACY_GROUP_ERROR);
}
return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), response);
}

@ -14,6 +14,9 @@
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv;
import static org.apache.logging.log4j.LogManager.getLogger;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.ENCLAVE_ERROR;
import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcEnclaveErrorConverter;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
@ -24,16 +27,19 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.Privac
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException;
import org.hyperledger.besu.ethereum.privacy.PrivacyController;
import org.hyperledger.besu.ethereum.privacy.PrivateTransaction;
import org.hyperledger.besu.ethereum.privacy.SendTransactionResponse;
import java.util.Base64;
import org.apache.logging.log4j.Logger;
import org.apache.tuweni.bytes.Bytes;
public class PrivDistributeRawTransaction implements JsonRpcMethod {
private static final Logger LOG = getLogger();
private final PrivacyController privacyController;
private final PrivacySendTransaction privacySendTransaction;
private final EnclavePublicKeyProvider enclavePublicKeyProvider;
@ -66,6 +72,9 @@ public class PrivDistributeRawTransaction implements JsonRpcMethod {
sendTransactionResponse =
privacyController.sendTransaction(
privateTransaction, enclavePublicKeyProvider.getEnclaveKey(requestContext.getUser()));
} catch (final MultiTenancyValidationException e) {
LOG.error("Unauthorized privacy multi-tenancy rpc request. {}", e.getMessage());
return new JsonRpcErrorResponse(requestContext.getRequest().getId(), ENCLAVE_ERROR);
} catch (final Exception e) {
return new JsonRpcErrorResponse(
requestContext.getRequest().getId(),

@ -15,16 +15,17 @@
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv;
import static org.apache.logging.log4j.LogManager.getLogger;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.FIND_PRIVACY_GROUP_ERROR;
import org.hyperledger.besu.enclave.types.PrivacyGroup;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.EnclavePublicKeyProvider;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException;
import org.hyperledger.besu.ethereum.privacy.PrivacyController;
import java.util.Arrays;
@ -63,10 +64,14 @@ public class PrivFindPrivacyGroup implements JsonRpcMethod {
privacyController.findPrivacyGroup(
Arrays.asList(addresses),
enclavePublicKeyProvider.getEnclaveKey(requestContext.getUser()));
} catch (Exception e) {
} catch (final MultiTenancyValidationException e) {
LOG.error("Unauthorized privacy multi-tenancy rpc request. {}", e.getMessage());
return new JsonRpcErrorResponse(
requestContext.getRequest().getId(), FIND_PRIVACY_GROUP_ERROR);
} catch (final Exception e) {
LOG.error("Failed to fetch privacy group", e);
return new JsonRpcErrorResponse(
requestContext.getRequest().getId(), JsonRpcError.FIND_PRIVACY_GROUP_ERROR);
requestContext.getRequest().getId(), FIND_PRIVACY_GROUP_ERROR);
}
return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), response);
}

@ -15,6 +15,7 @@
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv;
import static org.apache.logging.log4j.LogManager.getLogger;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.GET_PRIVATE_TRANSACTION_NONCE_ERROR;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
@ -26,6 +27,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcRespon
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException;
import org.hyperledger.besu.ethereum.privacy.PrivacyController;
import org.apache.logging.log4j.Logger;
@ -62,17 +64,21 @@ public class PrivGetEeaTransactionCount implements JsonRpcMethod {
try {
final long nonce =
privacyController.determineNonce(
privacyController.determineEeaNonce(
privateFrom,
privateFor,
address,
enclavePublicKeyProvider.getEnclaveKey(requestContext.getUser()));
return new JsonRpcSuccessResponse(
requestContext.getRequest().getId(), Quantity.create(nonce));
} catch (final MultiTenancyValidationException e) {
LOG.error("Unauthorized privacy multi-tenancy rpc request. {}", e.getMessage());
return new JsonRpcErrorResponse(
requestContext.getRequest().getId(), GET_PRIVATE_TRANSACTION_NONCE_ERROR);
} catch (final Exception e) {
LOG.error(e.getMessage(), e);
return new JsonRpcErrorResponse(
requestContext.getRequest().getId(), JsonRpcError.GET_PRIVATE_TRANSACTION_NONCE_ERROR);
requestContext.getRequest().getId(), GET_PRIVATE_TRANSACTION_NONCE_ERROR);
}
}
}

@ -22,6 +22,8 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.EnclavePublicKeyProvider;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.privacy.PrivateTransactionGroupResult;
@ -93,7 +95,8 @@ public class PrivGetPrivateTransaction implements JsonRpcMethod {
}
} catch (final Exception e) {
LOG.error("Failed to fetch private transaction", e);
return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), null);
return new JsonRpcErrorResponse(
requestContext.getRequest().getId(), JsonRpcError.ENCLAVE_ERROR);
}
}
}

@ -14,6 +14,9 @@
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv;
import static org.apache.logging.log4j.LogManager.getLogger;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.GET_PRIVATE_TRANSACTION_NONCE_ERROR;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod;
@ -24,10 +27,14 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcRespon
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException;
import org.hyperledger.besu.ethereum.privacy.PrivacyController;
import org.apache.logging.log4j.Logger;
public class PrivGetTransactionCount implements JsonRpcMethod {
private static final Logger LOG = getLogger();
private final PrivacyController privacyController;
private final EnclavePublicKeyProvider enclavePublicKeyProvider;
@ -53,11 +60,22 @@ public class PrivGetTransactionCount implements JsonRpcMethod {
final Address address = requestContext.getRequiredParameter(0, Address.class);
final String privacyGroupId = requestContext.getRequiredParameter(1, String.class);
final long nonce =
privacyController.determineNonce(
address,
privacyGroupId,
enclavePublicKeyProvider.getEnclaveKey(requestContext.getUser()));
return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), Quantity.create(nonce));
try {
final long nonce =
privacyController.determineBesuNonce(
address,
privacyGroupId,
enclavePublicKeyProvider.getEnclaveKey(requestContext.getUser()));
return new JsonRpcSuccessResponse(
requestContext.getRequest().getId(), Quantity.create(nonce));
} catch (final MultiTenancyValidationException e) {
LOG.error("Unauthorized privacy multi-tenancy rpc request. {}", e.getMessage());
return new JsonRpcErrorResponse(
requestContext.getRequest().getId(), GET_PRIVATE_TRANSACTION_NONCE_ERROR);
} catch (final Exception e) {
LOG.error(e.getMessage(), e);
return new JsonRpcErrorResponse(
requestContext.getRequest().getId(), GET_PRIVATE_TRANSACTION_NONCE_ERROR);
}
}
}

@ -81,7 +81,7 @@ public class PrivGetTransactionReceipt implements JsonRpcMethod {
final Hash transactionHash = requestContext.getRequiredParameter(0, Hash.class);
final Optional<TransactionLocation> maybeLocation =
blockchain.getBlockchain().getTransactionLocation(transactionHash);
if (!maybeLocation.isPresent()) {
if (maybeLocation.isEmpty()) {
return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), null);
}
final TransactionLocation location = maybeLocation.get();
@ -112,7 +112,7 @@ public class PrivGetTransactionReceipt implements JsonRpcMethod {
}
final String contractAddress =
!privateTransaction.getTo().isPresent()
privateTransaction.getTo().isEmpty()
? Address.privateContractAddress(
privateTransaction.getSender(),
privateTransaction.getNonce(),

@ -27,6 +27,8 @@ import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.privacy.DefaultPrivacyController;
import org.hyperledger.besu.ethereum.privacy.MultiTenancyPrivacyController;
import org.hyperledger.besu.ethereum.privacy.PrivacyController;
import org.hyperledger.besu.ethereum.privacy.markertransaction.FixedKeySigningPrivateMarkerTransactionFactory;
import org.hyperledger.besu.ethereum.privacy.markertransaction.PrivateMarkerTransactionFactory;
@ -75,21 +77,8 @@ public abstract class PrivacyApiGroupJsonRpcMethods extends ApiGroupJsonRpcMetho
final PrivateMarkerTransactionFactory markerTransactionFactory =
createPrivateMarkerTransactionFactory(
privacyParameters, blockchainQueries, transactionPool.getPendingTransactions());
final PrivacyController privacyController =
new PrivacyController(
privacyParameters, protocolSchedule.getChainId(), markerTransactionFactory);
final EnclavePublicKeyProvider enclavePublicProvider =
privacyParameters.isMultiTenancyEnabled()
? user ->
enclavePublicKey(user)
.orElseThrow(
() ->
new IllegalStateException(
"Request does not contain an authorization token"))
: user -> privacyParameters.getEnclavePublicKey();
final EnclavePublicKeyProvider enclavePublicProvider = createEnclavePublicKeyProvider();
final PrivacyController privacyController = createPrivacyController(markerTransactionFactory);
return create(privacyController, enclavePublicProvider).entrySet().stream()
.collect(
Collectors.toMap(
@ -117,6 +106,34 @@ public abstract class PrivacyApiGroupJsonRpcMethods extends ApiGroupJsonRpcMetho
return new RandomSigningPrivateMarkerTransactionFactory(privateContractAddress);
}
private EnclavePublicKeyProvider createEnclavePublicKeyProvider() {
return privacyParameters.isMultiTenancyEnabled()
? multiTenancyEnclavePublicKeyProvider()
: defaultEnclavePublicKeyProvider();
}
private EnclavePublicKeyProvider multiTenancyEnclavePublicKeyProvider() {
return user ->
enclavePublicKey(user)
.orElseThrow(
() -> new IllegalStateException("Request does not contain an authorization token"));
}
private EnclavePublicKeyProvider defaultEnclavePublicKeyProvider() {
return user -> privacyParameters.getEnclavePublicKey();
}
private PrivacyController createPrivacyController(
final PrivateMarkerTransactionFactory markerTransactionFactory) {
final DefaultPrivacyController defaultPrivacyController =
new DefaultPrivacyController(
privacyParameters, protocolSchedule.getChainId(), markerTransactionFactory);
return privacyParameters.isMultiTenancyEnabled()
? new MultiTenancyPrivacyController(
defaultPrivacyController, privacyParameters.getEnclave())
: defaultPrivacyController;
}
private JsonRpcMethod createPrivacyMethod(
final PrivacyParameters privacyParameters, final JsonRpcMethod rpcMethod) {
if (privacyParameters.isEnabled() && privacyParameters.isMultiTenancyEnabled()) {

@ -21,6 +21,7 @@ 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.methods.JsonRpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponseType;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
@ -87,8 +88,8 @@ public class MultiTenancyRpcMethodDecoratorTest {
assertThat(tokenRpcDecorator.getName()).isEqualTo("delegate");
final JsonRpcResponse response = tokenRpcDecorator.response(rpcRequestContext);
assertThat(response.getType()).isEqualTo(JsonRpcResponseType.UNAUTHORIZED);
final JsonRpcUnauthorizedResponse errorResponse = (JsonRpcUnauthorizedResponse) response;
assertThat(errorResponse.getError()).isEqualTo(JsonRpcError.UNAUTHORIZED);
assertThat(response.getType()).isEqualTo(JsonRpcResponseType.ERROR);
final JsonRpcErrorResponse errorResponse = (JsonRpcErrorResponse) response;
assertThat(errorResponse.getError()).isEqualTo(JsonRpcError.INVALID_REQUEST);
}
}

@ -19,11 +19,10 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.crypto.SECP256K1;
import org.hyperledger.besu.enclave.EnclaveServerException;
import org.hyperledger.besu.enclave.EnclaveClientException;
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.privacy.methods.EnclavePublicKeyProvider;
@ -37,6 +36,7 @@ import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool;
import org.hyperledger.besu.ethereum.mainnet.TransactionValidator.TransactionInvalidReason;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException;
import org.hyperledger.besu.ethereum.privacy.PrivacyController;
import org.hyperledger.besu.ethereum.privacy.PrivateTransaction;
import org.hyperledger.besu.ethereum.privacy.SendTransactionResponse;
@ -107,7 +107,7 @@ public class EeaSendRawTransactionTest {
"32886959230931919120748662916110619501838190146643992583529828535682419954515"),
new BigInteger(
"14473701025599600909210599917245952381483216609124029382871721729679842002948"),
Byte.valueOf("0")),
Byte.parseByte("0")),
Bytes.fromHexString("0x"),
Address.wrap(Bytes.fromHexString("0x8411b12666f68ef74cace3615c9d5a377729d03f")),
Optional.empty());
@ -290,13 +290,32 @@ public class EeaSendRawTransactionTest {
final JsonRpcResponse actualResponse = method.response(request);
assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse);
verifyZeroInteractions(privacyController);
verifyNoInteractions(privacyController);
}
@Test
public void invalidTransactionIsNotSentToTransactionPool() {
when(privacyController.sendTransaction(any(PrivateTransaction.class), any()))
.thenThrow(new EnclaveServerException(500, "enclave failed to execute"));
.thenThrow(new EnclaveClientException(400, "enclave failed to execute"));
final JsonRpcRequestContext request =
new JsonRpcRequestContext(
new JsonRpcRequest(
"2.0", "eea_sendRawTransaction", new String[] {VALID_PRIVATE_TRANSACTION_RLP}));
final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(request.getRequest().getId(), JsonRpcError.ENCLAVE_ERROR);
final JsonRpcResponse actualResponse = method.response(request);
assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse);
verifyNoInteractions(transactionPool);
}
@Test
public void invalidTransactionFailingWithMultiTenancyValidationErrorReturnsUnauthorizedError() {
when(privacyController.sendTransaction(any(PrivateTransaction.class), any()))
.thenThrow(new MultiTenancyValidationException("validation failed"));
final JsonRpcRequestContext request =
new JsonRpcRequestContext(

@ -22,7 +22,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.enclave.Enclave;
import org.hyperledger.besu.enclave.EnclaveServerException;
import org.hyperledger.besu.enclave.EnclaveClientException;
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;
@ -254,7 +254,7 @@ public class PrivCreatePrivacyGroupTest {
@Test
public void returnsCorrectErrorEnclaveError() {
when(privacyController.createPrivacyGroup(ADDRESSES, NAME, DESCRIPTION, ENCLAVE_PUBLIC_KEY))
.thenThrow(new EnclaveServerException(500, ""));
.thenThrow(new EnclaveClientException(400, "Enclave error"));
final PrivCreatePrivacyGroup privCreatePrivacyGroup =
new PrivCreatePrivacyGroup(privacyController, enclavePublicKeyProvider);

@ -20,13 +20,16 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.enclave.Enclave;
import org.hyperledger.besu.enclave.EnclaveClientException;
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.privacy.methods.EnclavePublicKeyProvider;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException;
import org.hyperledger.besu.ethereum.privacy.PrivacyController;
import io.vertx.core.json.JsonObject;
@ -75,7 +78,7 @@ public class PrivDeletePrivacyGroupTest {
@Test
public void failsWithDeletePrivacyGroupErrorIfEnclaveFails() {
when(privacyController.deletePrivacyGroup(PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY))
.thenThrow(new IllegalStateException("some failure"));
.thenThrow(new EnclaveClientException(500, "some failure"));
final PrivDeletePrivacyGroup privDeletePrivacyGroup =
new PrivDeletePrivacyGroup(privacyController, enclavePublicKeyProvider);
@ -85,4 +88,23 @@ public class PrivDeletePrivacyGroupTest {
assertThat(response.getError()).isEqualTo(JsonRpcError.DELETE_PRIVACY_GROUP_ERROR);
verify(privacyController).deletePrivacyGroup(PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY);
}
@Test
public void failsWithUnauthorizedErrorIfMultiTenancyValidationFails() {
when(privacyController.deletePrivacyGroup(PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY))
.thenThrow(new MultiTenancyValidationException("validation failed"));
final PrivDeletePrivacyGroup privDeletePrivacyGroup =
new PrivDeletePrivacyGroup(privacyController, enclavePublicKeyProvider);
final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(
request.getRequest().getId(), JsonRpcError.DELETE_PRIVACY_GROUP_ERROR);
final JsonRpcResponse response = privDeletePrivacyGroup.response(request);
assertThat(response).isInstanceOf(JsonRpcErrorResponse.class);
final JsonRpcErrorResponse errorResponse = (JsonRpcErrorResponse) response;
assertThat(errorResponse).isEqualTo(expectedResponse);
}
}

@ -23,9 +23,12 @@ import static org.mockito.Mockito.when;
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.privacy.methods.EnclavePublicKeyProvider;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException;
import org.hyperledger.besu.ethereum.privacy.PrivacyController;
import org.hyperledger.besu.ethereum.privacy.PrivateTransaction;
import org.hyperledger.besu.ethereum.privacy.SendTransactionResponse;
@ -98,4 +101,25 @@ public class PrivDistributeRawTransactionTest {
.validatePrivateTransaction(
any(PrivateTransaction.class), any(String.class), eq(ENCLAVE_PUBLIC_KEY));
}
@Test
public void invalidTransactionFailingWithMultiTenancyValidationErrorReturnsUnauthorizedError() {
when(privacyController.sendTransaction(any(PrivateTransaction.class), any()))
.thenThrow(new MultiTenancyValidationException("validation failed"));
final JsonRpcRequestContext request =
new JsonRpcRequestContext(
new JsonRpcRequest(
"2.0",
"priv_distributeRawTransaction",
new String[] {VALID_PRIVATE_TRANSACTION_RLP_PRIVACY_GROUP}),
user);
final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(request.getRequest().getId(), JsonRpcError.ENCLAVE_ERROR);
final JsonRpcResponse actualResponse = method.response(request);
assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse);
}
}

@ -20,14 +20,17 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.enclave.Enclave;
import org.hyperledger.besu.enclave.EnclaveClientException;
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;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.EnclavePublicKeyProvider;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException;
import org.hyperledger.besu.ethereum.privacy.PrivacyController;
import java.util.List;
@ -89,7 +92,7 @@ public class PrivFindPrivacyGroupTest {
@Test
public void failsWithFindPrivacyGroupErrorIfEnclaveFails() {
when(privacyController.findPrivacyGroup(ADDRESSES, ENCLAVE_PUBLIC_KEY))
.thenThrow(new IllegalStateException("some failure"));
.thenThrow(new EnclaveClientException(500, "some failure"));
final PrivFindPrivacyGroup privFindPrivacyGroup =
new PrivFindPrivacyGroup(privacyController, enclavePublicKeyProvider);
@ -98,4 +101,18 @@ public class PrivFindPrivacyGroupTest {
assertThat(response.getError()).isEqualTo(JsonRpcError.FIND_PRIVACY_GROUP_ERROR);
verify(privacyController).findPrivacyGroup(ADDRESSES, ENCLAVE_PUBLIC_KEY);
}
@Test
public void failsWithUnauthorizedErrorIfMultiTenancyValidationFails() {
when(privacyController.findPrivacyGroup(ADDRESSES, ENCLAVE_PUBLIC_KEY))
.thenThrow(new MultiTenancyValidationException("validation failed"));
final PrivFindPrivacyGroup privFindPrivacyGroup =
new PrivFindPrivacyGroup(privacyController, enclavePublicKeyProvider);
final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(
request.getRequest().getId(), JsonRpcError.FIND_PRIVACY_GROUP_ERROR);
final JsonRpcResponse response = privFindPrivacyGroup.response(request);
assertThat(response).isEqualTo(expectedResponse);
}
}

@ -12,22 +12,23 @@
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.privacy.eea;
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.enclave.EnclaveClientException;
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.privacy.methods.EnclavePublicKeyProvider;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv.PrivGetEeaTransactionCount;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException;
import org.hyperledger.besu.ethereum.privacy.PrivacyController;
import org.junit.Before;
@ -60,7 +61,7 @@ public class PrivGetEeaTransactionCountTest {
final PrivGetEeaTransactionCount method =
new PrivGetEeaTransactionCount(privacyController, enclavePublicKeyProvider);
when(privacyController.determineNonce(privateFrom, privateFor, address, ENCLAVE_PUBLIC_KEY))
when(privacyController.determineEeaNonce(privateFrom, privateFor, address, ENCLAVE_PUBLIC_KEY))
.thenReturn(reportedNonce);
final JsonRpcResponse response = method.response(request);
@ -76,8 +77,8 @@ public class PrivGetEeaTransactionCountTest {
final PrivGetEeaTransactionCount method =
new PrivGetEeaTransactionCount(privacyController, enclavePublicKeyProvider);
when(privacyController.determineNonce(privateFrom, privateFor, address, ENCLAVE_PUBLIC_KEY))
.thenThrow(RuntimeException.class);
when(privacyController.determineEeaNonce(privateFrom, privateFor, address, ENCLAVE_PUBLIC_KEY))
.thenThrow(EnclaveClientException.class);
final JsonRpcResponse response = method.response(request);
assertThat(response).isInstanceOf(JsonRpcErrorResponse.class);
@ -92,8 +93,24 @@ public class PrivGetEeaTransactionCountTest {
final PrivGetEeaTransactionCount method =
new PrivGetEeaTransactionCount(privacyController, enclavePublicKeyProvider);
when(privacyController.determineNonce(privateFrom, privateFor, address, ENCLAVE_PUBLIC_KEY))
.thenThrow(RuntimeException.class);
when(privacyController.determineEeaNonce(privateFrom, privateFor, address, ENCLAVE_PUBLIC_KEY))
.thenThrow(EnclaveClientException.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 failsWithUnauthorizedErrorIfMultiTenancyValidationFails() {
final PrivGetEeaTransactionCount method =
new PrivGetEeaTransactionCount(privacyController, enclavePublicKeyProvider);
when(privacyController.determineEeaNonce(privateFrom, privateFor, address, ENCLAVE_PUBLIC_KEY))
.thenThrow(new MultiTenancyValidationException("validation failed"));
final JsonRpcResponse response = method.response(request);
assertThat(response).isInstanceOf(JsonRpcErrorResponse.class);

@ -24,10 +24,14 @@ import static org.mockito.Mockito.when;
import org.hyperledger.besu.crypto.SECP256K1;
import org.hyperledger.besu.enclave.Enclave;
import org.hyperledger.besu.enclave.EnclaveClientException;
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;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.EnclavePublicKeyProvider;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.privacy.PrivateTransactionGroupResult;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.privacy.PrivateTransactionLegacyResult;
@ -190,4 +194,34 @@ public class PrivGetPrivateTransactionTest {
assertThat(result).isEqualToComparingFieldByField(privateTransactionGroupResult);
}
@Test
public void failsWithEnclaveErrorOnEnclaveError() {
when(blockchain.transactionByHash(any(Hash.class)))
.thenReturn(Optional.of(returnedTransaction));
when(returnedTransaction.getTransaction()).thenReturn(justTransaction);
when(justTransaction.getPayload()).thenReturn(Bytes.fromBase64String(""));
final PrivateTransaction privateTransaction =
privateTransactionBuilder
.privacyGroupId(Bytes.fromBase64String("Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs="))
.signAndBuild(KEY_PAIR);
final PrivGetPrivateTransaction privGetPrivateTransaction =
new PrivGetPrivateTransaction(blockchain, privacyController, enclavePublicKeyProvider);
final Object[] params = new Object[] {TRANSACTION_HASH};
final JsonRpcRequestContext request =
new JsonRpcRequestContext(new JsonRpcRequest("1", "priv_getPrivateTransaction", params));
final BytesValueRLPOutput bvrlp = new BytesValueRLPOutput();
privateTransaction.writeTo(bvrlp);
when(privacyController.retrieveTransaction(anyString(), any()))
.thenThrow(new EnclaveClientException(500, "enclave failure"));
final JsonRpcResponse response = privGetPrivateTransaction.response(request);
final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(request.getRequest().getId(), JsonRpcError.ENCLAVE_ERROR);
assertThat(response).isEqualTo(expectedResponse);
}
}

@ -20,12 +20,17 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.enclave.EnclaveClientException;
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.privacy.methods.EnclavePublicKeyProvider;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException;
import org.hyperledger.besu.ethereum.privacy.PrivacyController;
import io.vertx.core.json.JsonObject;
@ -53,7 +58,7 @@ public class PrivGetTransactionCountTest {
@Before
public void before() {
when(privacyParameters.isEnabled()).thenReturn(true);
when(privacyController.determineNonce(senderAddress, privacyGroupId, ENCLAVE_PUBLIC_KEY))
when(privacyController.determineBesuNonce(senderAddress, privacyGroupId, ENCLAVE_PUBLIC_KEY))
.thenReturn(NONCE);
}
@ -71,6 +76,46 @@ public class PrivGetTransactionCountTest {
(JsonRpcSuccessResponse) privGetTransactionCount.response(request);
assertThat(response.getResult()).isEqualTo(String.format("0x%X", NONCE));
verify(privacyController).determineNonce(senderAddress, privacyGroupId, ENCLAVE_PUBLIC_KEY);
verify(privacyController).determineBesuNonce(senderAddress, privacyGroupId, ENCLAVE_PUBLIC_KEY);
}
@Test
public void failsWithNonceErrorIfExceptionIsThrown() {
final PrivGetTransactionCount privGetTransactionCount =
new PrivGetTransactionCount(privacyController, enclavePublicKeyProvider);
when(privacyController.determineBesuNonce(senderAddress, privacyGroupId, ENCLAVE_PUBLIC_KEY))
.thenThrow(EnclaveClientException.class);
final Object[] params = new Object[] {senderAddress, privacyGroupId};
final JsonRpcRequestContext request =
new JsonRpcRequestContext(
new JsonRpcRequest("1", "priv_getTransactionCount", params), user);
final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(
request.getRequest().getId(), JsonRpcError.GET_PRIVATE_TRANSACTION_NONCE_ERROR);
final JsonRpcResponse response = privGetTransactionCount.response(request);
assertThat(response).isEqualTo(expectedResponse);
}
@Test
public void failsWithUnauthorizedErrorIfMultiTenancyValidationFails() {
final PrivGetTransactionCount privGetTransactionCount =
new PrivGetTransactionCount(privacyController, enclavePublicKeyProvider);
when(privacyController.determineBesuNonce(senderAddress, privacyGroupId, ENCLAVE_PUBLIC_KEY))
.thenThrow(new MultiTenancyValidationException("validation failed"));
final Object[] params = new Object[] {senderAddress, privacyGroupId};
final JsonRpcRequestContext request =
new JsonRpcRequestContext(
new JsonRpcRequest("1", "priv_getTransactionCount", params), user);
final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(
request.getRequest().getId(), JsonRpcError.GET_PRIVATE_TRANSACTION_NONCE_ERROR);
final JsonRpcResponse response = privGetTransactionCount.response(request);
assertThat(response).isEqualTo(expectedResponse);
}
}

@ -33,6 +33,7 @@ import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.privacy.MultiTenancyPrivacyController;
import org.hyperledger.besu.ethereum.privacy.PrivacyController;
import java.util.Map;
@ -147,6 +148,17 @@ public class PrivacyApiGroupJsonRpcMethodsTest {
assertThat(errorResponse.getError()).isEqualTo(PRIVACY_NOT_ENABLED);
}
@Test
public void rpcsCreatedWithMultiTenancyUseMultiTenancyController() {
when(privacyParameters.isEnabled()).thenReturn(true);
when(privacyParameters.isMultiTenancyEnabled()).thenReturn(true);
privacyApiGroupJsonRpcMethods.create();
final PrivacyController privacyController = privacyApiGroupJsonRpcMethods.privacyController;
assertThat(privacyController).isInstanceOf(MultiTenancyPrivacyController.class);
}
private User createUser(final String enclavePublicKey) {
return new JWTUser(new JsonObject().put("privacyPublicKey", enclavePublicKey), "");
}
@ -154,6 +166,7 @@ public class PrivacyApiGroupJsonRpcMethodsTest {
private static class TestPrivacyApiGroupJsonRpcMethods extends PrivacyApiGroupJsonRpcMethods {
private final JsonRpcMethod rpcMethod;
private PrivacyController privacyController;
private EnclavePublicKeyProvider enclavePublicKeyProvider;
public TestPrivacyApiGroupJsonRpcMethods(
@ -170,6 +183,7 @@ public class PrivacyApiGroupJsonRpcMethodsTest {
protected Map<String, JsonRpcMethod> create(
final PrivacyController privacyController,
final EnclavePublicKeyProvider enclavePublicKeyProvider) {
this.privacyController = privacyController;
this.enclavePublicKeyProvider = enclavePublicKeyProvider;
return mapOf(rpcMethod);
}

@ -0,0 +1,230 @@
/*
* 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.ethereum.privacy;
import org.hyperledger.besu.enclave.Enclave;
import org.hyperledger.besu.enclave.types.PrivacyGroup;
import org.hyperledger.besu.enclave.types.PrivacyGroup.Type;
import org.hyperledger.besu.enclave.types.ReceiveResponse;
import org.hyperledger.besu.enclave.types.SendResponse;
import org.hyperledger.besu.ethereum.core.Account;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.mainnet.TransactionValidator;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import org.hyperledger.besu.ethereum.privacy.markertransaction.PrivateMarkerTransactionFactory;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import java.math.BigInteger;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.tuweni.bytes.Bytes;
public class DefaultPrivacyController implements PrivacyController {
private static final Logger LOG = LogManager.getLogger();
private final Enclave enclave;
private final PrivateStateStorage privateStateStorage;
private final WorldStateArchive privateWorldStateArchive;
private final PrivateTransactionValidator privateTransactionValidator;
private final PrivateMarkerTransactionFactory privateMarkerTransactionFactory;
public DefaultPrivacyController(
final PrivacyParameters privacyParameters,
final Optional<BigInteger> chainId,
final PrivateMarkerTransactionFactory privateMarkerTransactionFactory) {
this(
privacyParameters.getEnclave(),
privacyParameters.getPrivateStateStorage(),
privacyParameters.getPrivateWorldStateArchive(),
new PrivateTransactionValidator(chainId),
privateMarkerTransactionFactory);
}
public DefaultPrivacyController(
final Enclave enclave,
final PrivateStateStorage privateStateStorage,
final WorldStateArchive privateWorldStateArchive,
final PrivateTransactionValidator privateTransactionValidator,
final PrivateMarkerTransactionFactory privateMarkerTransactionFactory) {
this.enclave = enclave;
this.privateStateStorage = privateStateStorage;
this.privateWorldStateArchive = privateWorldStateArchive;
this.privateTransactionValidator = privateTransactionValidator;
this.privateMarkerTransactionFactory = privateMarkerTransactionFactory;
}
@Override
public SendTransactionResponse sendTransaction(
final PrivateTransaction privateTransaction, final String enclavePublicKey) {
try {
LOG.trace("Storing private transaction in enclave");
final SendResponse sendResponse = sendRequest(privateTransaction, enclavePublicKey);
final String enclaveKey = sendResponse.getKey();
if (privateTransaction.getPrivacyGroupId().isPresent()) {
final String privacyGroupId = privateTransaction.getPrivacyGroupId().get().toBase64String();
return new SendTransactionResponse(enclaveKey, privacyGroupId);
} else {
final String privateFrom = privateTransaction.getPrivateFrom().toBase64String();
final String privacyGroupId = getPrivacyGroupId(enclaveKey, privateFrom);
return new SendTransactionResponse(enclaveKey, privacyGroupId);
}
} catch (Exception e) {
LOG.error("Failed to store private transaction in enclave", e);
throw e;
}
}
@Override
public ReceiveResponse retrieveTransaction(
final String enclaveKey, final String enclavePublicKey) {
return enclave.receive(enclaveKey, enclavePublicKey);
}
@Override
public PrivacyGroup createPrivacyGroup(
final List<String> addresses,
final String name,
final String description,
final String enclavePublicKey) {
return enclave.createPrivacyGroup(addresses, enclavePublicKey, name, description);
}
@Override
public String deletePrivacyGroup(final String privacyGroupId, final String enclavePublicKey) {
return enclave.deletePrivacyGroup(privacyGroupId, enclavePublicKey);
}
@Override
public PrivacyGroup[] findPrivacyGroup(
final List<String> addresses, final String enclavePublicKey) {
return enclave.findPrivacyGroup(addresses);
}
@Override
public Transaction createPrivacyMarkerTransaction(
final String transactionEnclaveKey, final PrivateTransaction privateTransaction) {
return privateMarkerTransactionFactory.create(transactionEnclaveKey, privateTransaction);
}
@Override
public ValidationResult<TransactionValidator.TransactionInvalidReason> validatePrivateTransaction(
final PrivateTransaction privateTransaction,
final String privacyGroupId,
final String enclavePublicKey) {
return privateTransactionValidator.validate(
privateTransaction,
determineBesuNonce(privateTransaction.getSender(), privacyGroupId, enclavePublicKey));
}
@Override
public long determineEeaNonce(
final String privateFrom,
final String[] privateFor,
final Address address,
final String enclavePublicKey) {
final List<String> groupMembers = Lists.asList(privateFrom, privateFor);
final List<PrivacyGroup> matchingGroups =
Lists.newArrayList(enclave.findPrivacyGroup(groupMembers));
final List<PrivacyGroup> legacyGroups =
matchingGroups.stream()
.filter(group -> group.getType() == Type.LEGACY)
.collect(Collectors.toList());
if (legacyGroups.size() == 0) {
// the legacy group does not exist yet
return 0;
}
Preconditions.checkArgument(
legacyGroups.size() == 1,
String.format(
"Found invalid number of privacy groups (%d), expected 1.", legacyGroups.size()));
final String privacyGroupId = legacyGroups.get(0).getPrivacyGroupId();
return determineBesuNonce(address, privacyGroupId, enclavePublicKey);
}
@Override
public long determineBesuNonce(
final Address sender, final String privacyGroupId, final String enclavePublicKey) {
return privateStateStorage
.getLatestStateRoot(Bytes.fromBase64String(privacyGroupId))
.map(
lastRootHash ->
privateWorldStateArchive
.getMutable(lastRootHash)
.map(
worldState -> {
final Account maybePrivateSender = worldState.get(sender);
if (maybePrivateSender != null) {
return maybePrivateSender.getNonce();
}
// account has not interacted in this private state
return Account.DEFAULT_NONCE;
})
// private state does not exist
.orElse(Account.DEFAULT_NONCE))
.orElse(
// private state does not exist
Account.DEFAULT_NONCE);
}
private SendResponse sendRequest(
final PrivateTransaction privateTransaction, final String enclavePublicKey) {
final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput();
privateTransaction.writeTo(rlpOutput);
final String payload = rlpOutput.encoded().toBase64String();
if (privateTransaction.getPrivacyGroupId().isPresent()) {
return enclave.send(
payload, enclavePublicKey, privateTransaction.getPrivacyGroupId().get().toBase64String());
} else {
final List<String> privateFor =
privateTransaction.getPrivateFor().get().stream()
.map(Bytes::toBase64String)
.collect(Collectors.toList());
if (privateFor.isEmpty()) {
privateFor.add(privateTransaction.getPrivateFrom().toBase64String());
}
return enclave.send(
payload, privateTransaction.getPrivateFrom().toBase64String(), privateFor);
}
}
private String getPrivacyGroupId(final String key, final String privateFrom) {
LOG.debug("Getting privacy group for key {} and privateFrom {}", key, privateFrom);
try {
return enclave.receive(key, privateFrom).getPrivacyGroupId();
} catch (final RuntimeException e) {
LOG.error("Failed to retrieve private transaction in enclave", e);
throw e;
}
}
}

@ -0,0 +1,133 @@
/*
* 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.ethereum.privacy;
import org.hyperledger.besu.enclave.Enclave;
import org.hyperledger.besu.enclave.types.PrivacyGroup;
import org.hyperledger.besu.enclave.types.ReceiveResponse;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.mainnet.TransactionValidator.TransactionInvalidReason;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import java.util.List;
public class MultiTenancyPrivacyController implements PrivacyController {
private final PrivacyController privacyController;
private final Enclave enclave;
public MultiTenancyPrivacyController(
final PrivacyController privacyController, final Enclave enclave) {
this.privacyController = privacyController;
this.enclave = enclave;
}
@Override
public SendTransactionResponse sendTransaction(
final PrivateTransaction privateTransaction, final String enclavePublicKey) {
verifyPrivateFromMatchesEnclavePublicKey(
privateTransaction.getPrivateFrom().toBase64String(), enclavePublicKey);
if (privateTransaction.getPrivacyGroupId().isPresent()) {
verifyPrivacyGroupContainsEnclavePublicKey(
privateTransaction.getPrivacyGroupId().get().toBase64String(), enclavePublicKey);
}
return privacyController.sendTransaction(privateTransaction, enclavePublicKey);
}
@Override
public ReceiveResponse retrieveTransaction(
final String enclaveKey, final String enclavePublicKey) {
// no validation necessary as the enclave receive only returns data for the enclave public key
return privacyController.retrieveTransaction(enclaveKey, enclavePublicKey);
}
@Override
public PrivacyGroup createPrivacyGroup(
final List<String> addresses,
final String name,
final String description,
final String enclavePublicKey) {
// no validation necessary as the enclave createPrivacyGroup fails if the addresses don't
// include the from (enclavePublicKey)
return privacyController.createPrivacyGroup(addresses, name, description, enclavePublicKey);
}
@Override
public String deletePrivacyGroup(final String privacyGroupId, final String enclavePublicKey) {
verifyPrivacyGroupContainsEnclavePublicKey(privacyGroupId, enclavePublicKey);
return privacyController.deletePrivacyGroup(privacyGroupId, enclavePublicKey);
}
@Override
public PrivacyGroup[] findPrivacyGroup(
final List<String> addresses, final String enclavePublicKey) {
if (!addresses.contains(enclavePublicKey)) {
throw new MultiTenancyValidationException(
"Privacy group addresses must contain the enclave public key");
}
return privacyController.findPrivacyGroup(addresses, enclavePublicKey);
}
@Override
public Transaction createPrivacyMarkerTransaction(
final String transactionEnclaveKey, final PrivateTransaction privateTransaction) {
return privacyController.createPrivacyMarkerTransaction(
transactionEnclaveKey, privateTransaction);
}
@Override
public ValidationResult<TransactionInvalidReason> validatePrivateTransaction(
final PrivateTransaction privateTransaction,
final String privacyGroupId,
final String enclavePublicKey) {
return privacyController.validatePrivateTransaction(
privateTransaction, privacyGroupId, enclavePublicKey);
}
@Override
public long determineEeaNonce(
final String privateFrom,
final String[] privateFor,
final Address address,
final String enclavePublicKey) {
verifyPrivateFromMatchesEnclavePublicKey(privateFrom, enclavePublicKey);
return privacyController.determineEeaNonce(privateFrom, privateFor, address, enclavePublicKey);
}
@Override
public long determineBesuNonce(
final Address sender, final String privacyGroupId, final String enclavePublicKey) {
verifyPrivacyGroupContainsEnclavePublicKey(privacyGroupId, enclavePublicKey);
return privacyController.determineBesuNonce(sender, privacyGroupId, enclavePublicKey);
}
private void verifyPrivateFromMatchesEnclavePublicKey(
final String privateFrom, final String enclavePublicKey) {
if (!privateFrom.equals(enclavePublicKey)) {
throw new MultiTenancyValidationException(
"Transaction privateFrom must match enclave public key");
}
}
private void verifyPrivacyGroupContainsEnclavePublicKey(
final String privacyGroupId, final String enclavePublicKey) {
final PrivacyGroup privacyGroup = enclave.retrievePrivacyGroup(privacyGroupId);
if (!privacyGroup.getMembers().contains(enclavePublicKey)) {
throw new MultiTenancyValidationException(
"Privacy group must contain the enclave public key");
}
}
}

@ -0,0 +1,22 @@
/*
* 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.ethereum.privacy;
public class MultiTenancyValidationException extends RuntimeException {
public MultiTenancyValidationException(final String message) {
super(message);
}
}

@ -14,208 +14,37 @@
*/
package org.hyperledger.besu.ethereum.privacy;
import org.hyperledger.besu.enclave.Enclave;
import org.hyperledger.besu.enclave.types.PrivacyGroup;
import org.hyperledger.besu.enclave.types.PrivacyGroup.Type;
import org.hyperledger.besu.enclave.types.ReceiveResponse;
import org.hyperledger.besu.enclave.types.SendResponse;
import org.hyperledger.besu.ethereum.core.Account;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.mainnet.TransactionValidator;
import org.hyperledger.besu.ethereum.mainnet.TransactionValidator.TransactionInvalidReason;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import org.hyperledger.besu.ethereum.privacy.markertransaction.PrivateMarkerTransactionFactory;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import java.math.BigInteger;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.tuweni.bytes.Bytes;
public interface PrivacyController {
public class PrivacyController {
SendTransactionResponse sendTransaction(
PrivateTransaction privateTransaction, String enclavePublicKey);
private static final Logger LOG = LogManager.getLogger();
ReceiveResponse retrieveTransaction(String enclaveKey, String enclavePublicKey);
private final Enclave enclave;
private final PrivateStateStorage privateStateStorage;
private final WorldStateArchive privateWorldStateArchive;
private final PrivateTransactionValidator privateTransactionValidator;
private final PrivateMarkerTransactionFactory privateMarkerTransactionFactory;
PrivacyGroup createPrivacyGroup(
List<String> addresses, String name, String description, String enclavePublicKey);
public PrivacyController(
final PrivacyParameters privacyParameters,
final Optional<BigInteger> chainId,
final PrivateMarkerTransactionFactory privateMarkerTransactionFactory) {
this(
privacyParameters.getEnclave(),
privacyParameters.getPrivateStateStorage(),
privacyParameters.getPrivateWorldStateArchive(),
new PrivateTransactionValidator(chainId),
privateMarkerTransactionFactory);
}
String deletePrivacyGroup(String privacyGroupId, String enclavePublicKey);
public PrivacyController(
final Enclave enclave,
final PrivateStateStorage privateStateStorage,
final WorldStateArchive privateWorldStateArchive,
final PrivateTransactionValidator privateTransactionValidator,
final PrivateMarkerTransactionFactory privateMarkerTransactionFactory) {
this.enclave = enclave;
this.privateStateStorage = privateStateStorage;
this.privateWorldStateArchive = privateWorldStateArchive;
this.privateTransactionValidator = privateTransactionValidator;
this.privateMarkerTransactionFactory = privateMarkerTransactionFactory;
}
PrivacyGroup[] findPrivacyGroup(List<String> addresses, String enclavePublicKey);
public SendTransactionResponse sendTransaction(
final PrivateTransaction privateTransaction, final String enclavePublicKey) {
try {
LOG.trace("Storing private transaction in enclave");
final SendResponse sendResponse = sendRequest(privateTransaction, enclavePublicKey);
final String enclaveKey = sendResponse.getKey();
if (privateTransaction.getPrivacyGroupId().isPresent()) {
final String privacyGroupId = privateTransaction.getPrivacyGroupId().get().toBase64String();
return new SendTransactionResponse(enclaveKey, privacyGroupId);
} else {
final String privateFrom = privateTransaction.getPrivateFrom().toBase64String();
final String privacyGroupId = getPrivacyGroupId(enclaveKey, privateFrom);
return new SendTransactionResponse(enclaveKey, privacyGroupId);
}
} catch (Exception e) {
LOG.error("Failed to store private transaction in enclave", e);
throw e;
}
}
Transaction createPrivacyMarkerTransaction(
String transactionEnclaveKey, PrivateTransaction privateTransaction);
public ReceiveResponse retrieveTransaction(
final String enclaveKey, final String enclavePublicKey) {
return enclave.receive(enclaveKey, enclavePublicKey);
}
ValidationResult<TransactionInvalidReason> validatePrivateTransaction(
PrivateTransaction privateTransaction, String privacyGroupId, String enclavePublicKey);
public PrivacyGroup createPrivacyGroup(
final List<String> addresses,
final String name,
final String description,
final String enclavePublicKey) {
return enclave.createPrivacyGroup(addresses, enclavePublicKey, name, description);
}
long determineEeaNonce(
String privateFrom, String[] privateFor, Address address, String enclavePublicKey);
public String deletePrivacyGroup(final String privacyGroupId, final String enclavePublicKey) {
return enclave.deletePrivacyGroup(privacyGroupId, enclavePublicKey);
}
public PrivacyGroup[] findPrivacyGroup(
final List<String> addresses, final String enclavePublicKey) {
return enclave.findPrivacyGroup(addresses);
}
public Transaction createPrivacyMarkerTransaction(
final String transactionEnclaveKey, final PrivateTransaction privateTransaction) {
return privateMarkerTransactionFactory.create(transactionEnclaveKey, privateTransaction);
}
public ValidationResult<TransactionValidator.TransactionInvalidReason> validatePrivateTransaction(
final PrivateTransaction privateTransaction,
final String privacyGroupId,
final String enclavePublicKey) {
return privateTransactionValidator.validate(
privateTransaction,
determineNonce(privateTransaction.getSender(), privacyGroupId, enclavePublicKey));
}
public long determineNonce(
final String privateFrom,
final String[] privateFor,
final Address address,
final String enclavePublicKey) {
final List<String> groupMembers = Lists.asList(privateFrom, privateFor);
final List<PrivacyGroup> matchingGroups =
Lists.newArrayList(enclave.findPrivacyGroup(groupMembers));
final List<PrivacyGroup> legacyGroups =
matchingGroups.stream()
.filter(group -> group.getType() == Type.LEGACY)
.collect(Collectors.toList());
if (legacyGroups.size() == 0) {
// the legacy group does not exist yet
return 0;
}
Preconditions.checkArgument(
legacyGroups.size() == 1,
String.format(
"Found invalid number of privacy groups (%d), expected 1.", legacyGroups.size()));
final String privacyGroupId = legacyGroups.get(0).getPrivacyGroupId();
return determineNonce(address, privacyGroupId, enclavePublicKey);
}
public long determineNonce(
final Address sender, final String privacyGroupId, final String enclavePublicKey) {
return privateStateStorage
.getLatestStateRoot(Bytes.fromBase64String(privacyGroupId))
.map(
lastRootHash ->
privateWorldStateArchive
.getMutable(lastRootHash)
.map(
worldState -> {
final Account maybePrivateSender = worldState.get(sender);
if (maybePrivateSender != null) {
return maybePrivateSender.getNonce();
}
// account has not interacted in this private state
return Account.DEFAULT_NONCE;
})
// private state does not exist
.orElse(Account.DEFAULT_NONCE))
.orElse(
// private state does not exist
Account.DEFAULT_NONCE);
}
private SendResponse sendRequest(
final PrivateTransaction privateTransaction, final String enclavePublicKey) {
final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput();
privateTransaction.writeTo(rlpOutput);
final String payload = rlpOutput.encoded().toBase64String();
if (privateTransaction.getPrivacyGroupId().isPresent()) {
return enclave.send(
payload, enclavePublicKey, privateTransaction.getPrivacyGroupId().get().toBase64String());
} else {
final List<String> privateFor =
privateTransaction.getPrivateFor().get().stream()
.map(Bytes::toBase64String)
.collect(Collectors.toList());
if (privateFor.isEmpty()) {
privateFor.add(privateTransaction.getPrivateFrom().toBase64String());
}
return enclave.send(
payload, privateTransaction.getPrivateFrom().toBase64String(), privateFor);
}
}
private String getPrivacyGroupId(final String key, final String privateFrom) {
LOG.debug("Getting privacy group for {}", privateFrom);
try {
return enclave.receive(key, privateFrom).getPrivacyGroupId();
} catch (final RuntimeException e) {
LOG.error("Failed to retrieve private transaction in enclave", e);
throw e;
}
}
long determineBesuNonce(Address sender, String privacyGroupId, String enclavePublicKey);
}

@ -63,7 +63,7 @@ import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class PrivacyControllerTest {
public class DefaultPrivacyControllerTest {
private static final String TRANSACTION_KEY = "93Ky7lXwFkMc7+ckoFgUMku5bpr9tz4zhmWmk9RlNng=";
private static final KeyPair KEY_PAIR =
@ -141,7 +141,7 @@ public class PrivacyControllerTest {
enclave = mockEnclave();
privacyController =
new PrivacyController(
new DefaultPrivacyController(
enclave,
privateStateStorage,
worldStateArchive,
@ -149,7 +149,7 @@ public class PrivacyControllerTest {
new FixedKeySigningPrivateMarkerTransactionFactory(
Address.DEFAULT_PRIVACY, (address) -> 0, KEY_PAIR));
brokenPrivacyController =
new PrivacyController(
new DefaultPrivacyController(
brokenMockEnclave(),
privateStateStorage,
worldStateArchive,
@ -330,7 +330,7 @@ public class PrivacyControllerTest {
when(account.getNonce()).thenReturn(8L);
final long nonce =
privacyController.determineNonce(
privacyController.determineEeaNonce(
"privateFrom", new String[] {"first", "second"}, address, ENCLAVE_PUBLIC_KEY);
assertThat(nonce).isEqualTo(reportedNonce);
@ -348,7 +348,7 @@ public class PrivacyControllerTest {
when(enclave.findPrivacyGroup(any())).thenReturn(returnedGroups);
final long nonce =
privacyController.determineNonce(
privacyController.determineEeaNonce(
"privateFrom", new String[] {"first", "second"}, address, ENCLAVE_PUBLIC_KEY);
assertThat(nonce).isEqualTo(reportedNonce);
@ -371,7 +371,7 @@ public class PrivacyControllerTest {
assertThatExceptionOfType(RuntimeException.class)
.isThrownBy(
() ->
privacyController.determineNonce(
privacyController.determineEeaNonce(
"privateFrom", new String[] {"first", "second"}, address, ENCLAVE_PUBLIC_KEY));
}
@ -381,7 +381,7 @@ public class PrivacyControllerTest {
when(account.getNonce()).thenReturn(4L);
final long nonce = privacyController.determineNonce(address, "Group1", ENCLAVE_PUBLIC_KEY);
final long nonce = privacyController.determineBesuNonce(address, "Group1", ENCLAVE_PUBLIC_KEY);
assertThat(nonce).isEqualTo(4L);
verify(privateStateStorage).getLatestStateRoot(Base64.decode("Group1"));
@ -396,7 +396,7 @@ public class PrivacyControllerTest {
when(privateStateStorage.getLatestStateRoot(Base64.decode("Group1")))
.thenReturn(Optional.empty());
final long nonce = privacyController.determineNonce(address, "Group1", ENCLAVE_PUBLIC_KEY);
final long nonce = privacyController.determineBesuNonce(address, "Group1", ENCLAVE_PUBLIC_KEY);
assertThat(nonce).isEqualTo(Account.DEFAULT_NONCE);
verifyNoInteractions(worldStateArchive, mutableWorldState, account);
@ -410,7 +410,7 @@ public class PrivacyControllerTest {
.thenReturn(Optional.of(hash));
when(worldStateArchive.getMutable(hash)).thenReturn(Optional.empty());
final long nonce = privacyController.determineNonce(address, "Group1", ENCLAVE_PUBLIC_KEY);
final long nonce = privacyController.determineBesuNonce(address, "Group1", ENCLAVE_PUBLIC_KEY);
assertThat(nonce).isEqualTo(Account.DEFAULT_NONCE);
verifyNoInteractions(mutableWorldState, account);
@ -425,7 +425,7 @@ public class PrivacyControllerTest {
when(worldStateArchive.getMutable(hash)).thenReturn(Optional.of(mutableWorldState));
when(mutableWorldState.get(address)).thenReturn(null);
final long nonce = privacyController.determineNonce(address, "Group1", ENCLAVE_PUBLIC_KEY);
final long nonce = privacyController.determineBesuNonce(address, "Group1", ENCLAVE_PUBLIC_KEY);
assertThat(nonce).isEqualTo(Account.DEFAULT_NONCE);
verifyNoInteractions(account);

@ -0,0 +1,313 @@
/*
* 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.ethereum.privacy;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.enclave.Enclave;
import org.hyperledger.besu.enclave.types.PrivacyGroup;
import org.hyperledger.besu.enclave.types.PrivacyGroup.Type;
import org.hyperledger.besu.enclave.types.ReceiveResponse;
import org.hyperledger.besu.ethereum.core.Address;
import java.util.List;
import org.apache.tuweni.bytes.Bytes;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class MultiTenancyPrivacyControllerTest {
private static final String ENCLAVE_PUBLIC_KEY1 = "Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs=";
private static final String ENCLAVE_PUBLIC_KEY2 = "OnviftjiizpjRt+HTuFBsKo2bVqD+nNlNYL5EE7y3Id=";
private static final String PRIVACY_GROUP_ID = "nNlNYL5EE7y3IdM=";
private static final String ENCLAVE_KEY = "Ko2bVqD";
@Mock private PrivacyController privacyController;
@Mock private Enclave enclave;
private MultiTenancyPrivacyController multiTenancyPrivacyController;
@Before
public void setup() {
multiTenancyPrivacyController = new MultiTenancyPrivacyController(privacyController, enclave);
}
@Test
public void
sendsEeaTransactionWithMatchingPrivateFromAndEnclavePublicKeyAndProducesSuccessfulResponse() {
final PrivateTransaction transaction =
PrivateTransaction.builder()
.privateFrom(Bytes.fromBase64String(ENCLAVE_PUBLIC_KEY1))
.build();
when(privacyController.sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1))
.thenReturn(new SendTransactionResponse(ENCLAVE_KEY, PRIVACY_GROUP_ID));
final SendTransactionResponse response =
multiTenancyPrivacyController.sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1);
assertThat(response.getEnclaveKey()).isEqualTo(ENCLAVE_KEY);
assertThat(response.getPrivacyGroupId()).isEqualTo(PRIVACY_GROUP_ID);
verify(privacyController).sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1);
}
@Test
public void
sendsBesuTransactionWithEnclavePublicKeyInPrivacyGroupAndProducesSuccessfulResponse() {
final PrivateTransaction transaction =
PrivateTransaction.builder()
.privateFrom(Bytes.fromBase64String(ENCLAVE_PUBLIC_KEY1))
.privacyGroupId(Bytes.fromBase64String(PRIVACY_GROUP_ID))
.build();
when(privacyController.sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1))
.thenReturn(new SendTransactionResponse(ENCLAVE_KEY, PRIVACY_GROUP_ID));
final PrivacyGroup privacyGroupWithEnclavePublicKey =
new PrivacyGroup(
PRIVACY_GROUP_ID,
Type.PANTHEON,
"",
"",
List.of(ENCLAVE_PUBLIC_KEY1, ENCLAVE_PUBLIC_KEY2));
when(enclave.retrievePrivacyGroup(PRIVACY_GROUP_ID))
.thenReturn(privacyGroupWithEnclavePublicKey);
final SendTransactionResponse response =
multiTenancyPrivacyController.sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1);
assertThat(response.getEnclaveKey()).isEqualTo(ENCLAVE_KEY);
assertThat(response.getPrivacyGroupId()).isEqualTo(PRIVACY_GROUP_ID);
verify(privacyController).sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1);
verify(enclave).retrievePrivacyGroup(PRIVACY_GROUP_ID);
}
@Test
public void sendEeaTransactionFailsWithValidationExceptionWhenPrivateFromDoesNotMatch() {
final PrivateTransaction transaction =
PrivateTransaction.builder()
.privateFrom(Bytes.fromBase64String(ENCLAVE_PUBLIC_KEY2))
.build();
assertThatThrownBy(
() -> multiTenancyPrivacyController.sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1))
.isInstanceOf(MultiTenancyValidationException.class)
.hasMessage("Transaction privateFrom must match enclave public key");
verify(privacyController, never()).sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1);
}
@Test
public void sendBesuTransactionFailsWithValidationExceptionWhenPrivateFromDoesNotMatch() {
final PrivateTransaction transaction =
PrivateTransaction.builder()
.privateFrom(Bytes.fromBase64String(ENCLAVE_PUBLIC_KEY2))
.privacyGroupId(Bytes.fromBase64String(PRIVACY_GROUP_ID))
.build();
assertThatThrownBy(
() -> multiTenancyPrivacyController.sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1))
.isInstanceOf(MultiTenancyValidationException.class)
.hasMessage("Transaction privateFrom must match enclave public key");
verify(privacyController, never()).sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1);
}
@Test
public void
sendBesuTransactionFailsWithValidationExceptionWhenPrivacyGroupDoesNotContainEnclavePublicKey() {
final PrivateTransaction transaction =
PrivateTransaction.builder()
.privateFrom(Bytes.fromBase64String(ENCLAVE_PUBLIC_KEY1))
.privacyGroupId(Bytes.fromBase64String(PRIVACY_GROUP_ID))
.build();
final PrivacyGroup privacyGroupWithoutEnclavePublicKey =
new PrivacyGroup(PRIVACY_GROUP_ID, Type.PANTHEON, "", "", List.of(ENCLAVE_PUBLIC_KEY2));
when(enclave.retrievePrivacyGroup(PRIVACY_GROUP_ID))
.thenReturn(privacyGroupWithoutEnclavePublicKey);
assertThatThrownBy(
() -> multiTenancyPrivacyController.sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1))
.isInstanceOf(MultiTenancyValidationException.class)
.hasMessage("Privacy group must contain the enclave public key");
verify(privacyController, never()).sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1);
}
@Test
public void retrieveTransactionDelegatesToPrivacyController() {
final ReceiveResponse delegateRetrieveResponse =
new ReceiveResponse(new byte[] {}, PRIVACY_GROUP_ID, ENCLAVE_KEY);
when(privacyController.retrieveTransaction(ENCLAVE_KEY, ENCLAVE_PUBLIC_KEY1))
.thenReturn(delegateRetrieveResponse);
final ReceiveResponse multiTenancyRetrieveResponse =
multiTenancyPrivacyController.retrieveTransaction(ENCLAVE_KEY, ENCLAVE_PUBLIC_KEY1);
assertThat(multiTenancyRetrieveResponse)
.isEqualToComparingFieldByField(delegateRetrieveResponse);
verify(privacyController).retrieveTransaction(ENCLAVE_KEY, ENCLAVE_PUBLIC_KEY1);
}
@Test
public void createPrivacyGroupDelegatesToPrivacyController() {
final List<String> addresses = List.of(ENCLAVE_PUBLIC_KEY1, ENCLAVE_PUBLIC_KEY2);
final PrivacyGroup delegatePrivacyGroup =
new PrivacyGroup(PRIVACY_GROUP_ID, Type.PANTHEON, "name", "description", addresses);
when(privacyController.createPrivacyGroup(
addresses, "name", "description", ENCLAVE_PUBLIC_KEY1))
.thenReturn(delegatePrivacyGroup);
final PrivacyGroup privacyGroup =
multiTenancyPrivacyController.createPrivacyGroup(
addresses, "name", "description", ENCLAVE_PUBLIC_KEY1);
assertThat(privacyGroup).isEqualToComparingFieldByField(delegatePrivacyGroup);
verify(privacyController)
.createPrivacyGroup(addresses, "name", "description", ENCLAVE_PUBLIC_KEY1);
}
@Test
public void deletesPrivacyGroupWhenEnclavePublicKeyInPrivacyGroup() {
final PrivacyGroup privacyGroupWithEnclavePublicKey =
new PrivacyGroup(
PRIVACY_GROUP_ID,
Type.PANTHEON,
"",
"",
List.of(ENCLAVE_PUBLIC_KEY1, ENCLAVE_PUBLIC_KEY2));
when(enclave.retrievePrivacyGroup(PRIVACY_GROUP_ID))
.thenReturn(privacyGroupWithEnclavePublicKey);
when(privacyController.deletePrivacyGroup(PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY1))
.thenReturn(ENCLAVE_PUBLIC_KEY1);
final String privacyGroupId =
multiTenancyPrivacyController.deletePrivacyGroup(PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY1);
assertThat(privacyGroupId).isEqualTo(ENCLAVE_PUBLIC_KEY1);
verify(privacyController).deletePrivacyGroup(PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY1);
}
@Test
public void
deletePrivacyGroupFailsWithValidationExceptionWhenPrivacyGroupDoesNotContainEnclavePublicKey() {
final PrivacyGroup privacyGroupWithoutEnclavePublicKey =
new PrivacyGroup(PRIVACY_GROUP_ID, Type.PANTHEON, "", "", List.of(ENCLAVE_PUBLIC_KEY2));
when(enclave.retrievePrivacyGroup(PRIVACY_GROUP_ID))
.thenReturn(privacyGroupWithoutEnclavePublicKey);
assertThatThrownBy(
() ->
multiTenancyPrivacyController.deletePrivacyGroup(
PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY1))
.isInstanceOf(MultiTenancyValidationException.class)
.hasMessage("Privacy group must contain the enclave public key");
}
@Test
public void findsPrivacyGroupWhenEnclavePublicKeyInAddresses() {
final List<String> addresses = List.of(ENCLAVE_PUBLIC_KEY1, ENCLAVE_PUBLIC_KEY2);
final PrivacyGroup privacyGroup =
new PrivacyGroup(PRIVACY_GROUP_ID, Type.PANTHEON, "", "", List.of(ENCLAVE_PUBLIC_KEY2));
when(privacyController.findPrivacyGroup(addresses, ENCLAVE_PUBLIC_KEY1))
.thenReturn(new PrivacyGroup[] {privacyGroup});
final PrivacyGroup[] privacyGroups =
multiTenancyPrivacyController.findPrivacyGroup(addresses, ENCLAVE_PUBLIC_KEY1);
assertThat(privacyGroups).hasSize(1);
assertThat(privacyGroups[0]).isEqualToComparingFieldByField(privacyGroup);
verify(privacyController).findPrivacyGroup(addresses, ENCLAVE_PUBLIC_KEY1);
}
@Test
public void findPrivacyGroupFailsWithValidationExceptionWhenEnclavePublicKeyNotInAddresses() {
final List<String> addresses = List.of(ENCLAVE_PUBLIC_KEY2);
assertThatThrownBy(
() -> multiTenancyPrivacyController.findPrivacyGroup(addresses, ENCLAVE_PUBLIC_KEY1))
.isInstanceOf(MultiTenancyValidationException.class)
.hasMessage("Privacy group addresses must contain the enclave public key");
}
@Test
public void determinesEeaNonceWhenPrivateFromMatchesEnclavePublicKey() {
final String[] privateFor = {ENCLAVE_PUBLIC_KEY2};
when(privacyController.determineEeaNonce(
ENCLAVE_PUBLIC_KEY1, privateFor, Address.ZERO, ENCLAVE_PUBLIC_KEY1))
.thenReturn(10L);
final long nonce =
multiTenancyPrivacyController.determineEeaNonce(
ENCLAVE_PUBLIC_KEY1, privateFor, Address.ZERO, ENCLAVE_PUBLIC_KEY1);
assertThat(nonce).isEqualTo(10);
verify(privacyController)
.determineEeaNonce(ENCLAVE_PUBLIC_KEY1, privateFor, Address.ZERO, ENCLAVE_PUBLIC_KEY1);
}
@Test
public void
determineEeaNonceFailsWithValidationExceptionWhenPrivateFromDoesNotMatchEnclavePublicKey() {
final String[] privateFor = {ENCLAVE_PUBLIC_KEY2};
assertThatThrownBy(
() ->
multiTenancyPrivacyController.determineEeaNonce(
ENCLAVE_PUBLIC_KEY2, privateFor, Address.ZERO, ENCLAVE_PUBLIC_KEY1))
.isInstanceOf(MultiTenancyValidationException.class)
.hasMessage("Transaction privateFrom must match enclave public key");
}
@Test
public void determineBesuNonceWhenEnclavePublicKeyInPrivacyGroup() {
final PrivacyGroup privacyGroupWithEnclavePublicKey =
new PrivacyGroup(
PRIVACY_GROUP_ID,
Type.PANTHEON,
"",
"",
List.of(ENCLAVE_PUBLIC_KEY1, ENCLAVE_PUBLIC_KEY2));
when(enclave.retrievePrivacyGroup(PRIVACY_GROUP_ID))
.thenReturn(privacyGroupWithEnclavePublicKey);
when(privacyController.determineBesuNonce(Address.ZERO, PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY1))
.thenReturn(10L);
final long nonce =
multiTenancyPrivacyController.determineBesuNonce(
Address.ZERO, PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY1);
assertThat(nonce).isEqualTo(10);
verify(privacyController)
.determineBesuNonce(Address.ZERO, PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY1);
}
@Test
public void
determineBesuNonceFailsWithValidationExceptionWhenEnclavePublicKeyNotInPrivacyGroup() {
final PrivacyGroup privacyGroupWithoutEnclavePublicKey =
new PrivacyGroup(PRIVACY_GROUP_ID, Type.PANTHEON, "", "", List.of(ENCLAVE_PUBLIC_KEY2));
when(enclave.retrievePrivacyGroup(PRIVACY_GROUP_ID))
.thenReturn(privacyGroupWithoutEnclavePublicKey);
assertThatThrownBy(
() ->
multiTenancyPrivacyController.determineBesuNonce(
Address.ZERO, PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY1))
.isInstanceOf(MultiTenancyValidationException.class)
.hasMessage("Privacy group must contain the enclave public key");
}
}
Loading…
Cancel
Save