diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivacySendTransaction.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivacySendTransaction.java deleted file mode 100644 index b2640e0926f..00000000000 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivacySendTransaction.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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.api.jsonrpc.internal.privacy.methods; - -import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcErrorConverter; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcRequestException; -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.privacy.PrivacyController; -import org.hyperledger.besu.ethereum.privacy.PrivateTransaction; -import org.hyperledger.besu.ethereum.privacy.Restriction; -import org.hyperledger.besu.ethereum.rlp.RLP; -import org.hyperledger.besu.ethereum.rlp.RLPException; - -import java.util.function.Supplier; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.tuweni.bytes.Bytes; - -public class PrivacySendTransaction { - - private static final Logger LOG = LogManager.getLogger(); - - protected final PrivacyController privacyController; - private final EnclavePublicKeyProvider enclavePublicKeyProvider; - - public PrivacySendTransaction( - final PrivacyController privacyController, - final EnclavePublicKeyProvider enclavePublicKeyProvider) { - this.privacyController = privacyController; - this.enclavePublicKeyProvider = enclavePublicKeyProvider; - } - - public PrivateTransaction validateAndDecodeRequest(final JsonRpcRequestContext request) - throws ErrorResponseException { - if (request.getRequest().getParamLength() != 1) { - throw new ErrorResponseException( - new JsonRpcErrorResponse(request.getRequest().getId(), JsonRpcError.INVALID_PARAMS)); - } - final String rawPrivateTransaction = request.getRequiredParameter(0, String.class); - final PrivateTransaction privateTransaction; - try { - privateTransaction = decodeRawTransaction(rawPrivateTransaction); - } catch (final InvalidJsonRpcRequestException e) { - throw new ErrorResponseException( - new JsonRpcErrorResponse(request.getRequest().getId(), JsonRpcError.DECODE_ERROR)); - } - if (!privateTransaction.getValue().isZero()) { - throw new ErrorResponseException( - new JsonRpcErrorResponse(request.getRequest().getId(), JsonRpcError.VALUE_NOT_ZERO)); - } - if (!privateTransaction.getRestriction().equals(Restriction.RESTRICTED)) { - throw new ErrorResponseException( - new JsonRpcErrorResponse( - request.getRequest().getId(), JsonRpcError.UNIMPLEMENTED_PRIVATE_TRANSACTION_TYPE)); - } - return privateTransaction; - } - - public JsonRpcResponse validateAndExecute( - final JsonRpcRequestContext request, - final PrivateTransaction privateTransaction, - final String privacyGroupId, - final Supplier successfulJsonRpcResponse) { - return privacyController - .validatePrivateTransaction( - privateTransaction, - privacyGroupId, - enclavePublicKeyProvider.getEnclaveKey(request.getUser())) - .either( - successfulJsonRpcResponse, - (errorReason) -> - new JsonRpcErrorResponse( - request.getRequest().getId(), - JsonRpcErrorConverter.convertTransactionInvalidReason(errorReason))); - } - - private PrivateTransaction decodeRawTransaction(final String hash) - throws InvalidJsonRpcRequestException { - try { - return PrivateTransaction.readFrom(RLP.input(Bytes.fromHexString(hash))); - } catch (final IllegalArgumentException | RLPException e) { - LOG.debug(e); - throw new InvalidJsonRpcRequestException("Invalid raw private transaction hex", e); - } - } - - public static class ErrorResponseException extends Exception { - private final JsonRpcResponse response; - - private ErrorResponseException(final JsonRpcResponse response) { - super(); - this.response = response; - } - - public JsonRpcResponse getResponse() { - return response; - } - } -} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransaction.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransaction.java index a7a049ba152..26b9aec2301 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransaction.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransaction.java @@ -16,34 +16,36 @@ 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.JsonRpcErrorConverter.convertTransactionInvalidReason; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.DECODE_ERROR; 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; 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.privacy.methods.PrivacySendTransaction; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.PrivacySendTransaction.ErrorResponseException; 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.Transaction; 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; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.rlp.RLPException; import org.apache.logging.log4j.Logger; +import org.apache.tuweni.bytes.Bytes; public class EeaSendRawTransaction implements JsonRpcMethod { private static final Logger LOG = getLogger(); - private final PrivacySendTransaction privacySendTransaction; - private final EnclavePublicKeyProvider enclavePublicKeyProvider; private final TransactionPool transactionPool; private final PrivacyController privacyController; + private final EnclavePublicKeyProvider enclavePublicKeyProvider; public EeaSendRawTransaction( final TransactionPool transactionPool, @@ -51,8 +53,6 @@ public EeaSendRawTransaction( final EnclavePublicKeyProvider enclavePublicKeyProvider) { this.transactionPool = transactionPool; this.privacyController = privacyController; - this.privacySendTransaction = - new PrivacySendTransaction(privacyController, enclavePublicKeyProvider); this.enclavePublicKeyProvider = enclavePublicKeyProvider; } @@ -63,45 +63,40 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final PrivateTransaction privateTransaction; - try { - privateTransaction = privacySendTransaction.validateAndDecodeRequest(requestContext); - } catch (final ErrorResponseException e) { - return e.getResponse(); - } + final String rawPrivateTransaction = requestContext.getRequiredParameter(0, String.class); + final Object id = requestContext.getRequest().getId(); - final SendTransactionResponse sendTransactionResponse; try { - sendTransactionResponse = - privacyController.sendTransaction( - privateTransaction, enclavePublicKeyProvider.getEnclaveKey(requestContext.getUser())); + final PrivateTransaction privateTransaction = + PrivateTransaction.readFrom(RLP.input(Bytes.fromHexString(rawPrivateTransaction))); + + final String enclavePublicKey = + enclavePublicKeyProvider.getEnclaveKey(requestContext.getUser()); + final ValidationResult validationResult = + privacyController.validatePrivateTransaction(privateTransaction, enclavePublicKey); + if (!validationResult.isValid()) { + return new JsonRpcErrorResponse( + id, convertTransactionInvalidReason(validationResult.getInvalidReason())); + } + + final String enclaveKey = + privacyController.sendTransaction(privateTransaction, enclavePublicKey); + final Transaction privacyMarkerTransaction = + privacyController.createPrivacyMarkerTransaction(enclaveKey, privateTransaction); + + return transactionPool + .addLocalTransaction(privacyMarkerTransaction) + .either( + () -> new JsonRpcSuccessResponse(id, privacyMarkerTransaction.getHash().toString()), + errorReason -> + new JsonRpcErrorResponse(id, convertTransactionInvalidReason(errorReason))); } catch (final MultiTenancyValidationException e) { LOG.error("Unauthorized privacy multi-tenancy rpc request. {}", e.getMessage()); - return new JsonRpcErrorResponse(requestContext.getRequest().getId(), ENCLAVE_ERROR); + return new JsonRpcErrorResponse(id, ENCLAVE_ERROR); + } catch (final IllegalArgumentException | RLPException e) { + return new JsonRpcErrorResponse(id, DECODE_ERROR); } catch (final Exception e) { - return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), convertEnclaveInvalidReason(e.getMessage())); + return new JsonRpcErrorResponse(id, convertEnclaveInvalidReason(e.getMessage())); } - - return privacySendTransaction.validateAndExecute( - requestContext, - privateTransaction, - sendTransactionResponse.getPrivacyGroupId(), - () -> { - final Transaction privacyMarkerTransaction = - privacyController.createPrivacyMarkerTransaction( - sendTransactionResponse.getEnclaveKey(), privateTransaction); - return transactionPool - .addLocalTransaction(privacyMarkerTransaction) - .either( - () -> - new JsonRpcSuccessResponse( - requestContext.getRequest().getId(), - privacyMarkerTransaction.getHash().toString()), - errorReason -> - new JsonRpcErrorResponse( - requestContext.getRequest().getId(), - JsonRpcErrorConverter.convertTransactionInvalidReason(errorReason))); - }); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDistributeRawTransaction.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDistributeRawTransaction.java index 96c67f0bbc0..dce8b7065cf 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDistributeRawTransaction.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDistributeRawTransaction.java @@ -15,22 +15,25 @@ 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.JsonRpcEnclaveErrorConverter.convertEnclaveInvalidReason; +import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcErrorConverter.convertTransactionInvalidReason; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.DECODE_ERROR; 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; 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.privacy.methods.PrivacySendTransaction; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.PrivacySendTransaction.ErrorResponseException; 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.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; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.rlp.RLPException; import java.util.Base64; @@ -41,15 +44,12 @@ public class PrivDistributeRawTransaction implements JsonRpcMethod { private static final Logger LOG = getLogger(); private final PrivacyController privacyController; - private final PrivacySendTransaction privacySendTransaction; private final EnclavePublicKeyProvider enclavePublicKeyProvider; public PrivDistributeRawTransaction( final PrivacyController privacyController, final EnclavePublicKeyProvider enclavePublicKeyProvider) { this.privacyController = privacyController; - this.privacySendTransaction = - new PrivacySendTransaction(privacyController, enclavePublicKeyProvider); this.enclavePublicKeyProvider = enclavePublicKeyProvider; } @@ -60,35 +60,36 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final PrivateTransaction privateTransaction; - try { - privateTransaction = privacySendTransaction.validateAndDecodeRequest(requestContext); - } catch (ErrorResponseException e) { - return e.getResponse(); - } + final String rawPrivateTransaction = requestContext.getRequiredParameter(0, String.class); + final Object id = requestContext.getRequest().getId(); - final SendTransactionResponse sendTransactionResponse; try { - sendTransactionResponse = - privacyController.sendTransaction( - privateTransaction, enclavePublicKeyProvider.getEnclaveKey(requestContext.getUser())); + final PrivateTransaction privateTransaction = + PrivateTransaction.readFrom(RLP.input(Bytes.fromHexString(rawPrivateTransaction))); + + final String enclavePublicKey = + enclavePublicKeyProvider.getEnclaveKey(requestContext.getUser()); + final ValidationResult validationResult = + privacyController.validatePrivateTransaction(privateTransaction, enclavePublicKey); + if (!validationResult.isValid()) { + return new JsonRpcErrorResponse( + id, convertTransactionInvalidReason(validationResult.getInvalidReason())); + } + + final String enclaveKey = + privacyController.sendTransaction(privateTransaction, enclavePublicKey); + return new JsonRpcSuccessResponse(id, hexEncodeEnclaveKey(enclaveKey)); } catch (final MultiTenancyValidationException e) { LOG.error("Unauthorized privacy multi-tenancy rpc request. {}", e.getMessage()); - return new JsonRpcErrorResponse(requestContext.getRequest().getId(), ENCLAVE_ERROR); + return new JsonRpcErrorResponse(id, ENCLAVE_ERROR); + } catch (final IllegalArgumentException | RLPException e) { + return new JsonRpcErrorResponse(id, DECODE_ERROR); } catch (final Exception e) { - return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), - JsonRpcEnclaveErrorConverter.convertEnclaveInvalidReason(e.getMessage())); + return new JsonRpcErrorResponse(id, convertEnclaveInvalidReason(e.getMessage())); } + } - return privacySendTransaction.validateAndExecute( - requestContext, - privateTransaction, - sendTransactionResponse.getPrivacyGroupId(), - () -> - new JsonRpcSuccessResponse( - requestContext.getRequest().getId(), - Bytes.wrap(Base64.getDecoder().decode(sendTransactionResponse.getEnclaveKey())) - .toHexString())); + private String hexEncodeEnclaveKey(final String enclaveKey) { + return Bytes.wrap(Base64.getDecoder().decode(enclaveKey)).toHexString(); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcErrorResponse.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcErrorResponse.java index 3da3fcca1c5..9c50d7da4c0 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcErrorResponse.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcErrorResponse.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.annotation.JsonGetter; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.google.common.base.MoreObjects; @JsonPropertyOrder({"jsonrpc", "id", "error"}) public class JsonRpcErrorResponse implements JsonRpcResponse { @@ -63,4 +64,9 @@ public boolean equals(final Object o) { public int hashCode() { return Objects.hash(id, error); } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("id", id).add("error", error).toString(); + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransactionTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransactionTest.java index da85ca3a965..3e5b1a65e87 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransactionTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransactionTest.java @@ -15,16 +15,20 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.eea; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.hyperledger.besu.ethereum.mainnet.TransactionValidator.TransactionInvalidReason.PRIVATE_TRANSACTION_FAILED; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import org.hyperledger.besu.crypto.SECP256K1; -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.exception.InvalidJsonRpcParameters; 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; @@ -39,9 +43,11 @@ 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.hyperledger.besu.ethereum.privacy.Restriction; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; import java.math.BigInteger; +import java.util.List; import java.util.Optional; import io.vertx.core.json.JsonObject; @@ -57,35 +63,11 @@ @RunWith(MockitoJUnitRunner.class) public class EeaSendRawTransactionTest { - private static final String VALUE_NON_ZERO_TRANSACTION_RLP = - "0xf88b808203e8832dc6c0808203e880820fe8a08b89005561f31ce861" - + "84949bf32087a9817b337ab1d6027d58ef4e48aea88bafa041b93a" - + "e41a99fe662ad7fc1406ac90bf6bd498b5fe56fd6bfea15de15714" - + "438eac41316156744d784c4355486d425648586f5a7a7a42675062" - + "572f776a3561784470573958386c393153476f3dc08a7265737472" - + "6963746564"; - - private static final String VALID_PRIVATE_TRANSACTION_RLP = - "0xf8f3800182520894095e7baea6a6c7c4c2dfeb977efac326af552d87" - + "808025a048b55bfa915ac795c431978d8a6a992b628d557da5ff" - + "759b307d495a36649353a01fffd310ac743f371de3b9f7f9cb56" - + "c0b28ad43601b4ab949f53faa07bd2c804ac41316156744d784c" - + "4355486d425648586f5a7a7a42675062572f776a356178447057" - + "3958386c393153476f3df85aac41316156744d784c4355486d42" - + "5648586f5a7a7a42675062572f776a3561784470573958386c39" - + "3153476f3dac4b6f32625671442b6e4e6c4e594c354545377933" - + "49644f6e766966746a69697a706a52742b4854754642733d8a72" - + "657374726963746564"; - + private static final String VALID_PRIVATE_TRANSACTION_RLP = validPrivateTransactionRlp(); private static final String VALID_PRIVATE_TRANSACTION_RLP_PRIVACY_GROUP = - "0xf8ac800182520894095e7baea6a6c7c4c2dfeb977efac326af552d87" - + "80801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff" - + "759b307d495a36649353a01fffd310ac743f371de3b9f7f9cb56" - + "c0b28ad43601b4ab949f53faa07bd2c804a0035695b4cc4b0941" - + "e60551d7a19cf30603db5bfc23e5ac43a56f57f25f75486aa00f" - + "200e885ff29e973e2576b6600181d1b0a2b5294e30d9be4a1981" - + "ffb33a0b8c8a72657374726963746564"; + validPrivateTransactionRlpPrivacyGroup(); + // RLP encode fails creating a transaction without privateFrom so must be manually encoded private static final String PRIVATE_TRANSACTION_RLP_PRIVACY_GROUP_NO_PRIVATE_FROM = "0xf88b800182520894095e7baea6a6c7c4c2dfeb977efac326af55" + "2d8780801ba048b55bfa915ac795c431978d8a6a992b628d55" @@ -114,15 +96,12 @@ public class EeaSendRawTransactionTest { private static final String ENCLAVE_PUBLIC_KEY = "A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; private final String MOCK_ORION_KEY = ""; - private final String MOCK_PRIVACY_GROUP = ""; private final User user = new JWTUser(new JsonObject().put("privacyPublicKey", ENCLAVE_PUBLIC_KEY), ""); private final EnclavePublicKeyProvider enclavePublicKeyProvider = (user) -> ENCLAVE_PUBLIC_KEY; @Mock private TransactionPool transactionPool; - @Mock private EeaSendRawTransaction method; - @Mock private PrivacyController privacyController; @Before @@ -137,12 +116,9 @@ public void requestIsMissingParameter() { new JsonRpcRequestContext( new JsonRpcRequest("2.0", "eea_sendRawTransaction", new String[] {})); - final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(request.getRequest().getId(), JsonRpcError.INVALID_PARAMS); - - final JsonRpcResponse actualResponse = method.response(request); - - assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse); + assertThatThrownBy(() -> method.response(request)) + .isInstanceOf(InvalidJsonRpcParameters.class) + .hasMessage("Missing required json rpc parameter at index 0"); } @Test @@ -150,12 +126,9 @@ public void requestHasNullObjectParameter() { final JsonRpcRequestContext request = new JsonRpcRequestContext(new JsonRpcRequest("2.0", "eea_sendRawTransaction", null)); - final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(request.getRequest().getId(), JsonRpcError.INVALID_PARAMS); - - final JsonRpcResponse actualResponse = method.response(request); - - assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse); + assertThatThrownBy(() -> method.response(request)) + .isInstanceOf(InvalidJsonRpcParameters.class) + .hasMessage("Missing required json rpc parameter at index 0"); } @Test @@ -164,12 +137,9 @@ public void requestHasNullArrayParameter() { new JsonRpcRequestContext( new JsonRpcRequest("2.0", "eea_sendRawTransaction", new String[] {null})); - final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(request.getRequest().getId(), JsonRpcError.INVALID_PARAMS); - - final JsonRpcResponse actualResponse = method.response(request); - - assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse); + assertThatThrownBy(() -> method.response(request)) + .isInstanceOf(InvalidJsonRpcParameters.class) + .hasMessage("Missing required json rpc parameter at index 0"); } @Test @@ -188,27 +158,12 @@ public void invalidTransactionRlpDecoding() { assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse); } - @Test - public void valueNonZeroTransaction() { - final JsonRpcRequestContext request = - new JsonRpcRequestContext( - new JsonRpcRequest( - "2.0", "eea_sendRawTransaction", new String[] {VALUE_NON_ZERO_TRANSACTION_RLP})); - - final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(request.getRequest().getId(), JsonRpcError.VALUE_NOT_ZERO); - - final JsonRpcResponse actualResponse = method.response(request); - - assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse); - } - @Test public void validTransactionIsSentToTransactionPool() { when(privacyController.sendTransaction(any(PrivateTransaction.class), any())) - .thenReturn(new SendTransactionResponse(MOCK_ORION_KEY, MOCK_PRIVACY_GROUP)); + .thenReturn(MOCK_ORION_KEY); when(privacyController.validatePrivateTransaction( - any(PrivateTransaction.class), any(String.class), any())) + any(PrivateTransaction.class), any(String.class))) .thenReturn(ValidationResult.valid()); when(privacyController.createPrivacyMarkerTransaction( any(String.class), any(PrivateTransaction.class))) @@ -232,8 +187,7 @@ public void validTransactionIsSentToTransactionPool() { verify(privacyController) .sendTransaction(any(PrivateTransaction.class), eq(ENCLAVE_PUBLIC_KEY)); verify(privacyController) - .validatePrivateTransaction( - any(PrivateTransaction.class), any(String.class), eq(ENCLAVE_PUBLIC_KEY)); + .validatePrivateTransaction(any(PrivateTransaction.class), eq(ENCLAVE_PUBLIC_KEY)); verify(privacyController) .createPrivacyMarkerTransaction(any(String.class), any(PrivateTransaction.class)); verify(transactionPool).addLocalTransaction(any(Transaction.class)); @@ -242,9 +196,8 @@ public void validTransactionIsSentToTransactionPool() { @Test public void validTransactionPrivacyGroupIsSentToTransactionPool() { when(privacyController.sendTransaction(any(PrivateTransaction.class), any())) - .thenReturn(new SendTransactionResponse(MOCK_ORION_KEY, MOCK_PRIVACY_GROUP)); - when(privacyController.validatePrivateTransaction( - any(PrivateTransaction.class), any(String.class), any())) + .thenReturn(MOCK_ORION_KEY); + when(privacyController.validatePrivateTransaction(any(PrivateTransaction.class), anyString())) .thenReturn(ValidationResult.valid()); when(privacyController.createPrivacyMarkerTransaction( any(String.class), any(PrivateTransaction.class))) @@ -269,7 +222,7 @@ public void validTransactionPrivacyGroupIsSentToTransactionPool() { assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse); verify(privacyController).sendTransaction(any(PrivateTransaction.class), any()); verify(privacyController) - .validatePrivateTransaction(any(PrivateTransaction.class), any(String.class), any()); + .validatePrivateTransaction(any(PrivateTransaction.class), anyString()); verify(privacyController) .createPrivacyMarkerTransaction(any(String.class), any(PrivateTransaction.class)); verify(transactionPool).addLocalTransaction(any(Transaction.class)); @@ -294,9 +247,9 @@ public void invalidTransactionWithoutPrivateFromFieldFailsWithDecodeError() { } @Test - public void invalidTransactionIsNotSentToTransactionPool() { - when(privacyController.sendTransaction(any(PrivateTransaction.class), any())) - .thenThrow(new EnclaveClientException(400, "enclave failed to execute")); + public void invalidTransactionIsNotSentToEnclaveAndIsNotAddedToTransactionPool() { + when(privacyController.validatePrivateTransaction(any(PrivateTransaction.class), anyString())) + .thenReturn(ValidationResult.invalid(PRIVATE_TRANSACTION_FAILED)); final JsonRpcRequestContext request = new JsonRpcRequestContext( @@ -304,16 +257,19 @@ public void invalidTransactionIsNotSentToTransactionPool() { "2.0", "eea_sendRawTransaction", new String[] {VALID_PRIVATE_TRANSACTION_RLP})); final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(request.getRequest().getId(), JsonRpcError.ENCLAVE_ERROR); + new JsonRpcErrorResponse(request.getRequest().getId(), JsonRpcError.INVALID_PARAMS); final JsonRpcResponse actualResponse = method.response(request); assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse); + verify(privacyController, never()).sendTransaction(any(), any()); verifyNoInteractions(transactionPool); } @Test public void invalidTransactionFailingWithMultiTenancyValidationErrorReturnsUnauthorizedError() { + when(privacyController.validatePrivateTransaction(any(PrivateTransaction.class), anyString())) + .thenReturn(ValidationResult.valid()); when(privacyController.sendTransaction(any(PrivateTransaction.class), any())) .thenThrow(new MultiTenancyValidationException("validation failed")); @@ -379,9 +335,8 @@ private void verifyErrorForInvalidTransaction( final TransactionInvalidReason transactionInvalidReason, final JsonRpcError expectedError) { when(privacyController.sendTransaction(any(PrivateTransaction.class), any())) - .thenReturn(new SendTransactionResponse(MOCK_ORION_KEY, MOCK_PRIVACY_GROUP)); - when(privacyController.validatePrivateTransaction( - any(PrivateTransaction.class), any(String.class), any())) + .thenReturn(MOCK_ORION_KEY); + when(privacyController.validatePrivateTransaction(any(PrivateTransaction.class), anyString())) .thenReturn(ValidationResult.valid()); when(privacyController.createPrivacyMarkerTransaction( any(String.class), any(PrivateTransaction.class))) @@ -401,7 +356,7 @@ private void verifyErrorForInvalidTransaction( assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse); verify(privacyController).sendTransaction(any(PrivateTransaction.class), any()); verify(privacyController) - .validatePrivateTransaction(any(PrivateTransaction.class), any(String.class), any()); + .validatePrivateTransaction(any(PrivateTransaction.class), anyString()); verify(privacyController) .createPrivacyMarkerTransaction(any(String.class), any(PrivateTransaction.class)); verify(transactionPool).addLocalTransaction(any(Transaction.class)); @@ -411,4 +366,53 @@ private void verifyErrorForInvalidTransaction( public void getMethodReturnsExpectedName() { assertThat(method.getName()).matches("eea_sendRawTransaction"); } + + private static String validPrivateTransactionRlp() { + final PrivateTransaction.Builder privateTransactionBuilder = + PrivateTransaction.builder() + .nonce(0) + .gasPrice(Wei.of(1)) + .gasLimit(21000) + .value(Wei.ZERO) + .payload(Bytes.EMPTY) + .to(Address.fromHexString("0x095e7baea6a6c7c4c2dfeb977efac326af552d87")) + .chainId(BigInteger.ONE) + .privateFrom(Bytes.fromBase64String("S28yYlZxRCtuTmxOWUw1RUU3eTNJZE9udmlmdGppaXp=")) + .privateFor( + List.of( + Bytes.fromBase64String("S28yYlZxRCtuTmxOWUw1RUU3eTNJZE9udmlmdGppaXp="), + Bytes.fromBase64String("QTFhVnRNeExDVUhtQlZIWG9aenpCZ1BiVy93ajVheER="))) + .restriction(Restriction.RESTRICTED); + return rlpEncodeTransaction(privateTransactionBuilder); + } + + private static String validPrivateTransactionRlpPrivacyGroup() { + final PrivateTransaction.Builder privateTransactionBuilder = + PrivateTransaction.builder() + .nonce(0) + .gasPrice(Wei.of(1)) + .gasLimit(21000) + .value(Wei.ZERO) + .payload(Bytes.EMPTY) + .to(Address.fromHexString("0x095e7baea6a6c7c4c2dfeb977efac326af552d87")) + .chainId(BigInteger.ONE) + .privateFrom(Bytes.fromBase64String("S28yYlZxRCtuTmxOWUw1RUU3eTNJZE9udmlmdGppaXp=")) + .privacyGroupId(Bytes.fromBase64String("DyAOiF/ynpc+JXa2YAGB0bCitSlOMNm+ShmB/7M6C4w=")) + .restriction(Restriction.RESTRICTED); + return rlpEncodeTransaction(privateTransactionBuilder); + } + + private static String rlpEncodeTransaction( + final PrivateTransaction.Builder privateTransactionBuilder) { + final SECP256K1.KeyPair keyPair = + SECP256K1.KeyPair.create( + SECP256K1.PrivateKey.create( + new BigInteger( + "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63", 16))); + + final PrivateTransaction privateTransaction = privateTransactionBuilder.signAndBuild(keyPair); + final BytesValueRLPOutput bvrlp = new BytesValueRLPOutput(); + privateTransaction.writeTo(bvrlp); + return bvrlp.encoded().toHexString(); + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDistributeRawTransactionTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDistributeRawTransactionTest.java index 2c8be2c71d5..02100f8a065 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDistributeRawTransactionTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDistributeRawTransactionTest.java @@ -16,6 +16,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -31,7 +32,6 @@ 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; @@ -74,9 +74,8 @@ public void before() { public void validTransactionHashReturnedAfterDistribute() { final String enclavePublicKey = "93Ky7lXwFkMc7+ckoFgUMku5bpr9tz4zhmWmk9RlNng="; when(privacyController.sendTransaction(any(PrivateTransaction.class), any())) - .thenReturn(new SendTransactionResponse(enclavePublicKey, "")); - when(privacyController.validatePrivateTransaction( - any(PrivateTransaction.class), any(String.class), any())) + .thenReturn(enclavePublicKey); + when(privacyController.validatePrivateTransaction(any(PrivateTransaction.class), anyString())) .thenReturn(ValidationResult.valid()); final JsonRpcRequestContext request = @@ -98,14 +97,15 @@ public void validTransactionHashReturnedAfterDistribute() { verify(privacyController) .sendTransaction(any(PrivateTransaction.class), eq(ENCLAVE_PUBLIC_KEY)); verify(privacyController) - .validatePrivateTransaction( - any(PrivateTransaction.class), any(String.class), eq(ENCLAVE_PUBLIC_KEY)); + .validatePrivateTransaction(any(PrivateTransaction.class), eq(ENCLAVE_PUBLIC_KEY)); } @Test public void invalidTransactionFailingWithMultiTenancyValidationErrorReturnsUnauthorizedError() { when(privacyController.sendTransaction(any(PrivateTransaction.class), any())) .thenThrow(new MultiTenancyValidationException("validation failed")); + when(privacyController.validatePrivateTransaction(any(PrivateTransaction.class), anyString())) + .thenReturn(ValidationResult.valid()); final JsonRpcRequestContext request = new JsonRpcRequestContext( diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidator.java index b235f81b146..2902cb2f069 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidator.java @@ -75,6 +75,8 @@ enum TransactionInvalidReason { PRIVATE_NONCE_TOO_LOW, PRIVACY_GROUP_DOES_NOT_EXIST, INCORRECT_PRIVATE_NONCE, - GAS_PRICE_TOO_LOW; + GAS_PRICE_TOO_LOW, + PRIVATE_VALUE_NOT_ZERO, + PRIVATE_UNIMPLEMENTED_TRANSACTION_TYPE; } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/DefaultPrivacyController.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/DefaultPrivacyController.java index 77a369e5d51..09a402aacc7 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/DefaultPrivacyController.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/DefaultPrivacyController.java @@ -83,21 +83,13 @@ public DefaultPrivacyController( } @Override - public SendTransactionResponse sendTransaction( + public String 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 (final Exception e) { + return sendResponse.getKey(); + } catch (Exception e) { LOG.error("Failed to store private transaction in enclave", e); throw e; } @@ -137,9 +129,8 @@ public Transaction createPrivacyMarkerTransaction( @Override public ValidationResult validatePrivateTransaction( - final PrivateTransaction privateTransaction, - final String privacyGroupId, - final String enclavePublicKey) { + final PrivateTransaction privateTransaction, final String enclavePublicKey) { + final String privacyGroupId = privateTransaction.determinePrivacyGroupId(); return privateTransactionValidator.validate( privateTransaction, determineBesuNonce(privateTransaction.getSender(), privacyGroupId, enclavePublicKey)); @@ -234,14 +225,4 @@ private SendResponse sendRequest( 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; - } - } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyController.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyController.java index c0c3df14ffa..78badbfe64f 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyController.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyController.java @@ -38,7 +38,7 @@ public MultiTenancyPrivacyController( } @Override - public SendTransactionResponse sendTransaction( + public String sendTransaction( final PrivateTransaction privateTransaction, final String enclavePublicKey) { verifyPrivateFromMatchesEnclavePublicKey( privateTransaction.getPrivateFrom().toBase64String(), enclavePublicKey); @@ -92,11 +92,8 @@ public Transaction createPrivacyMarkerTransaction( @Override public ValidationResult validatePrivateTransaction( - final PrivateTransaction privateTransaction, - final String privacyGroupId, - final String enclavePublicKey) { - return privacyController.validatePrivateTransaction( - privateTransaction, privacyGroupId, enclavePublicKey); + final PrivateTransaction privateTransaction, final String enclavePublicKey) { + return privacyController.validatePrivateTransaction(privateTransaction, enclavePublicKey); } @Override diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivacyController.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivacyController.java index 09577da143f..1c92609acfb 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivacyController.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivacyController.java @@ -27,8 +27,7 @@ public interface PrivacyController { - SendTransactionResponse sendTransaction( - PrivateTransaction privateTransaction, String enclavePublicKey); + String sendTransaction(PrivateTransaction privateTransaction, String enclavePublicKey); ReceiveResponse retrieveTransaction(String enclaveKey, String enclavePublicKey); @@ -43,7 +42,7 @@ Transaction createPrivacyMarkerTransaction( String transactionEnclaveKey, PrivateTransaction privateTransaction); ValidationResult validatePrivateTransaction( - PrivateTransaction privateTransaction, String privacyGroupId, String enclavePublicKey); + PrivateTransaction privateTransaction, String enclavePublicKey); long determineEeaNonce( String privateFrom, String[] privateFor, Address address, String enclavePublicKey); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivacyGroupUtil.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivacyGroupUtil.java new file mode 100644 index 00000000000..c693986e424 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivacyGroupUtil.java @@ -0,0 +1,50 @@ +/* + * 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.crypto.Hash; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.tuweni.bytes.Bytes; + +public class PrivacyGroupUtil { + + public static String calculateEeaPrivacyGroupId( + final Bytes privateFrom, final List privateFor) { + final List privacyGroupIds = new ArrayList<>(); + privacyGroupIds.add(privateFrom); + privacyGroupIds.addAll(privateFor); + + final List sortedPrivacyGroupIds = + privacyGroupIds.stream() + .distinct() + .map(Bytes::toArray) + .sorted(Comparator.comparing(Arrays::hashCode)) + .collect(Collectors.toList()); + + final BytesValueRLPOutput bytesValueRLPOutput = new BytesValueRLPOutput(); + bytesValueRLPOutput.writeList( + sortedPrivacyGroupIds, + (privacyGroupId, rlpOutput) -> rlpOutput.writeBytes(Bytes.of(privacyGroupId))); + + return Hash.keccak256(bytesValueRLPOutput.encoded()).toBase64String(); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransaction.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransaction.java index a07bcc94224..e488bdcea82 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransaction.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransaction.java @@ -32,6 +32,7 @@ import java.util.Objects; import java.util.Optional; +import com.google.common.collect.Lists; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; @@ -454,6 +455,21 @@ public Wei getUpfrontCost() { return getUpfrontGasCost().add(getValue()); } + /** + * Determines the privacy group id. Either returning the value of privacyGroupId field if it + * exists or calculating the EEA privacyGroupId from the privateFrom and privateFor fields. + * + * @return the privacyGroupId + */ + public String determinePrivacyGroupId() { + if (getPrivacyGroupId().isPresent()) { + return getPrivacyGroupId().get().toBase64String(); + } else { + final List privateFor = getPrivateFor().orElse(Lists.newArrayList()); + return PrivacyGroupUtil.calculateEeaPrivacyGroupId(getPrivateFrom(), privateFor); + } + } + private static Bytes32 computeSenderRecoveryHash( final long nonce, final Wei gasPrice, diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionValidator.java index 4b92ce74211..c1a42e911a8 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionValidator.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.privacy; import org.hyperledger.besu.ethereum.mainnet.TransactionValidator; +import org.hyperledger.besu.ethereum.mainnet.TransactionValidator.TransactionInvalidReason; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import java.math.BigInteger; @@ -35,6 +36,16 @@ public PrivateTransactionValidator(final Optional chainId) { public ValidationResult validate( final PrivateTransaction transaction, final Long accountNonce) { + LOG.debug("Validating private transaction fields of {}", transaction.hash()); + final ValidationResult privateFieldsValidationResult = + validatePrivateTransactionFields(transaction); + if (!privateFieldsValidationResult.isValid()) { + LOG.debug( + "Private Transaction fields are invalid {}, {}", + transaction.hash(), + privateFieldsValidationResult.getErrorMessage()); + return privateFieldsValidationResult; + } LOG.debug("Validating the signature of Private Transaction {} ", transaction.hash()); @@ -76,6 +87,20 @@ public ValidationResult validate( return ValidationResult.valid(); } + private ValidationResult + validatePrivateTransactionFields(final PrivateTransaction privateTransaction) { + if (!privateTransaction.getValue().isZero()) { + return ValidationResult.invalid( + TransactionValidator.TransactionInvalidReason.PRIVATE_VALUE_NOT_ZERO); + } + if (!privateTransaction.getRestriction().equals(Restriction.RESTRICTED)) { + return ValidationResult.invalid( + TransactionValidator.TransactionInvalidReason.PRIVATE_UNIMPLEMENTED_TRANSACTION_TYPE); + } + + return ValidationResult.valid(); + } + private ValidationResult validateTransactionSignature(final PrivateTransaction transaction) { if (chainId.isPresent() @@ -87,7 +112,7 @@ public ValidationResult validate( transaction.getChainId().get(), chainId.get())); } - if (!chainId.isPresent() && transaction.getChainId().isPresent()) { + if (chainId.isEmpty() && transaction.getChainId().isPresent()) { return ValidationResult.invalid( TransactionValidator.TransactionInvalidReason.REPLAY_PROTECTED_SIGNATURES_NOT_SUPPORTED, "Replay protection (chainId) is not supported"); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/SendTransactionResponse.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/SendTransactionResponse.java deleted file mode 100644 index cb78955996e..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/SendTransactionResponse.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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 SendTransactionResponse { - private final String enclaveKey; - private final String privacyGroupId; - - public SendTransactionResponse(final String enclaveKey, final String privacyGroupId) { - this.enclaveKey = enclaveKey; - this.privacyGroupId = privacyGroupId; - } - - public String getEnclaveKey() { - return enclaveKey; - } - - public String getPrivacyGroupId() { - return privacyGroupId; - } -} diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/PrivateTransactionTestFixture.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/PrivateTransactionTestFixture.java index 3ab3f288fa9..66fa2c522f4 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/PrivateTransactionTestFixture.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/PrivateTransactionTestFixture.java @@ -36,7 +36,7 @@ public class PrivateTransactionTestFixture { private Optional
to = Optional.empty(); private Address sender = Address.fromHexString(String.format("%020x", 1)); - private Wei value = Wei.of(4); + private Wei value = Wei.of(0); private Bytes payload = Bytes.EMPTY; @@ -52,7 +52,7 @@ public class PrivateTransactionTestFixture { private Optional privacyGroupId = Optional.empty(); - private final Restriction restriction = Restriction.RESTRICTED; + private Restriction restriction = Restriction.RESTRICTED; public PrivateTransaction createTransaction(final KeyPair keys) { final PrivateTransaction.Builder builder = PrivateTransaction.builder(); @@ -137,4 +137,9 @@ public PrivateTransactionTestFixture privacyGroupId(final Bytes privacyGroupId) this.privacyGroupId = Optional.of(privacyGroupId); return this; } + + public PrivateTransactionTestFixture restriction(final Restriction restriction) { + this.restriction = restriction; + return this; + } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/DefaultPrivacyControllerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/DefaultPrivacyControllerTest.java index e7049ddeaf2..6c3a7e54aae 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/DefaultPrivacyControllerTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/DefaultPrivacyControllerTest.java @@ -86,7 +86,6 @@ public class DefaultPrivacyControllerTest { private PrivacyController privacyController; private PrivacyController brokenPrivacyController; private PrivateTransactionValidator privateTransactionValidator; - private PrivateTransactionSimulator privateTransactionSimulator; private Enclave enclave; private Account account; private String enclavePublicKey; @@ -154,7 +153,8 @@ public void setUp() throws Exception { enclavePublicKey = OrionKeyUtils.loadKey("orion_key_0.pub"); privateTransactionValidator = mockPrivateTransactionValidator(); enclave = mockEnclave(); - privateTransactionSimulator = mockPrivateTransactionSimulator(); + final PrivateTransactionSimulator privateTransactionSimulator = + mockPrivateTransactionSimulator(); privacyController = new DefaultPrivacyController( @@ -181,16 +181,13 @@ public void sendsValidLegacyTransaction() { final PrivateTransaction transaction = buildLegacyPrivateTransaction(1); - final SendTransactionResponse sendTransactionResponse = - privacyController.sendTransaction(transaction, ENCLAVE_PUBLIC_KEY); + final String enclaveKey = privacyController.sendTransaction(transaction, ENCLAVE_PUBLIC_KEY); final ValidationResult validationResult = - privacyController.validatePrivateTransaction( - transaction, sendTransactionResponse.getPrivacyGroupId(), ENCLAVE_PUBLIC_KEY); + privacyController.validatePrivateTransaction(transaction, ENCLAVE_PUBLIC_KEY); final Transaction markerTransaction = - privacyController.createPrivacyMarkerTransaction( - sendTransactionResponse.getEnclaveKey(), transaction); + privacyController.createPrivacyMarkerTransaction(enclaveKey, transaction); assertThat(validationResult).isEqualTo(ValidationResult.valid()); assertThat(markerTransaction.contractAddress()).isEqualTo(PUBLIC_TRANSACTION.contractAddress()); @@ -207,16 +204,13 @@ public void sendValidBesuTransaction() { final PrivateTransaction transaction = buildBesuPrivateTransaction(1); - final SendTransactionResponse sendTransactionResponse = - privacyController.sendTransaction(transaction, ENCLAVE_PUBLIC_KEY); + final String enclaveKey = privacyController.sendTransaction(transaction, ENCLAVE_PUBLIC_KEY); final ValidationResult validationResult = - privacyController.validatePrivateTransaction( - transaction, transaction.getPrivacyGroupId().get().toString(), ENCLAVE_PUBLIC_KEY); + privacyController.validatePrivateTransaction(transaction, ENCLAVE_PUBLIC_KEY); final Transaction markerTransaction = - privacyController.createPrivacyMarkerTransaction( - sendTransactionResponse.getEnclaveKey(), transaction); + privacyController.createPrivacyMarkerTransaction(enclaveKey, transaction); assertThat(validationResult).isEqualTo(ValidationResult.valid()); assertThat(markerTransaction.contractAddress()).isEqualTo(PUBLIC_TRANSACTION.contractAddress()); @@ -242,11 +236,8 @@ public void validateTransactionWithTooLowNonceReturnsError() { .thenReturn(ValidationResult.invalid(PRIVATE_NONCE_TOO_LOW)); final PrivateTransaction transaction = buildLegacyPrivateTransaction(0); - final SendTransactionResponse sendTransactionResponse = - privacyController.sendTransaction(transaction, ENCLAVE_PUBLIC_KEY); final ValidationResult validationResult = - privacyController.validatePrivateTransaction( - transaction, sendTransactionResponse.getPrivacyGroupId(), ENCLAVE_PUBLIC_KEY); + privacyController.validatePrivateTransaction(transaction, ENCLAVE_PUBLIC_KEY); assertThat(validationResult).isEqualTo(ValidationResult.invalid(PRIVATE_NONCE_TOO_LOW)); } @@ -257,11 +248,8 @@ public void validateTransactionWithIncorrectNonceReturnsError() { final PrivateTransaction transaction = buildLegacyPrivateTransaction(2); - final SendTransactionResponse sendTransactionResponse = - privacyController.sendTransaction(transaction, ENCLAVE_PUBLIC_KEY); final ValidationResult validationResult = - privacyController.validatePrivateTransaction( - transaction, sendTransactionResponse.getPrivacyGroupId(), ENCLAVE_PUBLIC_KEY); + privacyController.validatePrivateTransaction(transaction, ENCLAVE_PUBLIC_KEY); assertThat(validationResult).isEqualTo(ValidationResult.invalid(INCORRECT_PRIVATE_NONCE)); } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyControllerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyControllerTest.java index 2e797beee2c..e5469eb6883 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyControllerTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyControllerTest.java @@ -70,12 +70,11 @@ public void setup() { .build(); when(privacyController.sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1)) - .thenReturn(new SendTransactionResponse(ENCLAVE_KEY, PRIVACY_GROUP_ID)); + .thenReturn(ENCLAVE_KEY); - final SendTransactionResponse response = + final String enclaveKey = multiTenancyPrivacyController.sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1); - assertThat(response.getEnclaveKey()).isEqualTo(ENCLAVE_KEY); - assertThat(response.getPrivacyGroupId()).isEqualTo(PRIVACY_GROUP_ID); + assertThat(enclaveKey).isEqualTo(ENCLAVE_KEY); verify(privacyController).sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1); } @@ -89,7 +88,7 @@ public void setup() { .build(); when(privacyController.sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1)) - .thenReturn(new SendTransactionResponse(ENCLAVE_KEY, PRIVACY_GROUP_ID)); + .thenReturn(ENCLAVE_KEY); final PrivacyGroup privacyGroupWithEnclavePublicKey = new PrivacyGroup( PRIVACY_GROUP_ID, @@ -100,10 +99,9 @@ public void setup() { when(enclave.retrievePrivacyGroup(PRIVACY_GROUP_ID)) .thenReturn(privacyGroupWithEnclavePublicKey); - final SendTransactionResponse response = + final String response = multiTenancyPrivacyController.sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1); - assertThat(response.getEnclaveKey()).isEqualTo(ENCLAVE_KEY); - assertThat(response.getPrivacyGroupId()).isEqualTo(PRIVACY_GROUP_ID); + assertThat(response).isEqualTo(ENCLAVE_KEY); verify(privacyController).sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1); verify(enclave).retrievePrivacyGroup(PRIVACY_GROUP_ID); } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/PrivacyGroupUtilTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/PrivacyGroupUtilTest.java new file mode 100644 index 00000000000..9d1fad56fbf --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/PrivacyGroupUtilTest.java @@ -0,0 +1,84 @@ +/* + * 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 java.util.Arrays; +import java.util.stream.Collectors; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.Test; + +public class PrivacyGroupUtilTest { + + private static final String ENCLAVE_PUBLIC_KEY_1 = "negmDcN2P4ODpqn/6WkJ02zT/0w0bjhGpkZ8UP6vARk="; + private static final String ENCLAVE_PUBLIC_KEY_2 = "g59BmTeJIn7HIcnq8VQWgyh/pDbvbt2eyP0Ii60aDDw="; + private static final String ENCLAVE_PUBLIC_KEY_3 = "6fg8q5rWMBoAT2oIiU3tYJbk4b7oAr7dxaaVY7TeM3U="; + + // This is the expected generated legacy privacy group id for a privacy group containing the + // enclave public keys enclave_public_key_1, enclave_public_key_2 and enclave_public_key_3 + private static final String EXPECTED_LEGACY_PRIVACY_GROUP_ID = + "/xzRjCLioUBkm5LYuzll61GXyrD5x7bvXzQk/ovJA/4="; + + @Test + public void calculatesPrivacyGroupIdWithPrivateFromAndEmptyPrivateFor() { + final String expected = "kAbelwaVW7okoEn1+okO+AbA4Hhz/7DaCOWVQz9nx5M="; + assertThat(privacyGroupId(ENCLAVE_PUBLIC_KEY_1)).isEqualTo(expected); + } + + @Test + public void calculatesSamePrivacyGroupIdForDuplicateValues() { + assertThat( + privacyGroupId( + ENCLAVE_PUBLIC_KEY_2, + ENCLAVE_PUBLIC_KEY_1, + ENCLAVE_PUBLIC_KEY_3, + ENCLAVE_PUBLIC_KEY_1)) + .isEqualTo(EXPECTED_LEGACY_PRIVACY_GROUP_ID); + assertThat( + privacyGroupId( + ENCLAVE_PUBLIC_KEY_2, + ENCLAVE_PUBLIC_KEY_1, + ENCLAVE_PUBLIC_KEY_1, + ENCLAVE_PUBLIC_KEY_3)) + .isEqualTo(EXPECTED_LEGACY_PRIVACY_GROUP_ID); + assertThat(privacyGroupId(ENCLAVE_PUBLIC_KEY_2, ENCLAVE_PUBLIC_KEY_1, ENCLAVE_PUBLIC_KEY_3)) + .isEqualTo(EXPECTED_LEGACY_PRIVACY_GROUP_ID); + } + + @Test + public void calculatesSamePrivacyGroupIdForPrivateForInDifferentOrders() { + final String expectedPrivacyGroupId = "/xzRjCLioUBkm5LYuzll61GXyrD5x7bvXzQk/ovJA/4="; + assertThat(privacyGroupId(ENCLAVE_PUBLIC_KEY_1, ENCLAVE_PUBLIC_KEY_2, ENCLAVE_PUBLIC_KEY_3)) + .isEqualTo(expectedPrivacyGroupId); + assertThat(privacyGroupId(ENCLAVE_PUBLIC_KEY_1, ENCLAVE_PUBLIC_KEY_3, ENCLAVE_PUBLIC_KEY_2)) + .isEqualTo(expectedPrivacyGroupId); + assertThat(privacyGroupId(ENCLAVE_PUBLIC_KEY_2, ENCLAVE_PUBLIC_KEY_1, ENCLAVE_PUBLIC_KEY_3)) + .isEqualTo(expectedPrivacyGroupId); + assertThat(privacyGroupId(ENCLAVE_PUBLIC_KEY_2, ENCLAVE_PUBLIC_KEY_3, ENCLAVE_PUBLIC_KEY_1)) + .isEqualTo(expectedPrivacyGroupId); + assertThat(privacyGroupId(ENCLAVE_PUBLIC_KEY_3, ENCLAVE_PUBLIC_KEY_1, ENCLAVE_PUBLIC_KEY_2)) + .isEqualTo(expectedPrivacyGroupId); + assertThat(privacyGroupId(ENCLAVE_PUBLIC_KEY_3, ENCLAVE_PUBLIC_KEY_2, ENCLAVE_PUBLIC_KEY_1)) + .isEqualTo(expectedPrivacyGroupId); + } + + private String privacyGroupId(final String privateFrom, final String... privateFor) { + return PrivacyGroupUtil.calculateEeaPrivacyGroupId( + Bytes.fromBase64String(privateFrom), + Arrays.stream(privateFor).map(Bytes::fromBase64String).collect(Collectors.toList())); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionValidatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionValidatorTest.java index 92244c70c1c..a12e0a5861b 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionValidatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionValidatorTest.java @@ -18,12 +18,15 @@ import static org.hyperledger.besu.ethereum.mainnet.TransactionValidator.TransactionInvalidReason.INCORRECT_PRIVATE_NONCE; import static org.hyperledger.besu.ethereum.mainnet.TransactionValidator.TransactionInvalidReason.INVALID_SIGNATURE; import static org.hyperledger.besu.ethereum.mainnet.TransactionValidator.TransactionInvalidReason.PRIVATE_NONCE_TOO_LOW; +import static org.hyperledger.besu.ethereum.mainnet.TransactionValidator.TransactionInvalidReason.PRIVATE_UNIMPLEMENTED_TRANSACTION_TYPE; +import static org.hyperledger.besu.ethereum.mainnet.TransactionValidator.TransactionInvalidReason.PRIVATE_VALUE_NOT_ZERO; import static org.hyperledger.besu.ethereum.mainnet.TransactionValidator.TransactionInvalidReason.REPLAY_PROTECTED_SIGNATURES_NOT_SUPPORTED; import static org.hyperledger.besu.ethereum.mainnet.TransactionValidator.TransactionInvalidReason.WRONG_CHAIN_ID; import static org.mockito.Mockito.when; import org.hyperledger.besu.crypto.SECP256K1.KeyPair; import org.hyperledger.besu.ethereum.core.PrivateTransactionTestFixture; +import org.hyperledger.besu.ethereum.core.Wei; import org.hyperledger.besu.ethereum.mainnet.TransactionValidator.TransactionInvalidReason; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; @@ -103,18 +106,60 @@ public void transactionWithInvalidSignatureShouldReturnInvalidSignature() { assertThat(validationResult).isEqualTo(ValidationResult.invalid(INVALID_SIGNATURE)); } + @Test + public void transactionWithNonZeroValueShouldReturnValueNotZeroError() { + validator = new PrivateTransactionValidator(Optional.of(BigInteger.ONE)); + + ValidationResult validationResult = + validator.validate(privateTransactionWithValue(1L), 0L); + + assertThat(validationResult).isEqualTo(ValidationResult.invalid(PRIVATE_VALUE_NOT_ZERO)); + } + + @Test + public void + transactionWithUnrestrictedTransactionTypeShouldReturnUnimplementedTransactionTypeError() { + validator = new PrivateTransactionValidator(Optional.of(BigInteger.ONE)); + + ValidationResult validationResult = + validator.validate(privateTransactionWithRestriction(Restriction.UNRESTRICTED), 0L); + + assertThat(validationResult) + .isEqualTo(ValidationResult.invalid(PRIVATE_UNIMPLEMENTED_TRANSACTION_TYPE)); + } + + @Test + public void + transactionWithUnsupportedTransactionTypeShouldReturnUnimplementedTransactionTypeError() { + validator = new PrivateTransactionValidator(Optional.of(BigInteger.ONE)); + + ValidationResult validationResult = + validator.validate(privateTransactionWithRestriction(Restriction.UNSUPPORTED), 0L); + + assertThat(validationResult) + .isEqualTo(ValidationResult.invalid(PRIVATE_UNIMPLEMENTED_TRANSACTION_TYPE)); + } + private PrivateTransaction privateTransactionWithNonce(final long nonce) { - PrivateTransactionTestFixture privateTransactionTestFixture = - new PrivateTransactionTestFixture(); - privateTransactionTestFixture.nonce(nonce); - privateTransactionTestFixture.chainId(Optional.empty()); - return privateTransactionTestFixture.createTransaction(senderKeys); + return new PrivateTransactionTestFixture() + .nonce(nonce) + .chainId(Optional.empty()) + .createTransaction(senderKeys); } private PrivateTransaction privateTransactionWithChainId(final int chainId) { - PrivateTransactionTestFixture privateTransactionTestFixture = - new PrivateTransactionTestFixture(); - privateTransactionTestFixture.chainId(Optional.of(BigInteger.valueOf(chainId))); - return privateTransactionTestFixture.createTransaction(senderKeys); + return new PrivateTransactionTestFixture() + .chainId(Optional.of(BigInteger.valueOf(chainId))) + .createTransaction(senderKeys); + } + + private PrivateTransaction privateTransactionWithValue(final long value) { + return new PrivateTransactionTestFixture().value(Wei.of(value)).createTransaction(senderKeys); + } + + private PrivateTransaction privateTransactionWithRestriction(final Restriction restriction) { + return new PrivateTransactionTestFixture() + .restriction(restriction) + .createTransaction(senderKeys); } }