diff --git a/container-tests/tests/src/test/java/org/hyperledger/besu/tests/container/ContainerTests.java b/container-tests/tests/src/test/java/org/hyperledger/besu/tests/container/ContainerTests.java index 5a6051ae74..717025b093 100644 --- a/container-tests/tests/src/test/java/org/hyperledger/besu/tests/container/ContainerTests.java +++ b/container-tests/tests/src/test/java/org/hyperledger/besu/tests/container/ContainerTests.java @@ -18,7 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.fail; import static org.hyperledger.besu.tests.container.helpers.ContractOperations.deployContractAndReturnAddress; -import static org.hyperledger.besu.tests.container.helpers.ContractOperations.generateHexString; +import static org.hyperledger.besu.tests.container.helpers.ContractOperations.generate64BytesHexString; import static org.hyperledger.besu.tests.container.helpers.ContractOperations.getCode; import static org.hyperledger.besu.tests.container.helpers.ContractOperations.getTransactionLog; import static org.hyperledger.besu.tests.container.helpers.ContractOperations.sendLogEventAndReturnTransactionHash; @@ -30,6 +30,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import io.reactivex.disposables.Disposable; import okhttp3.OkHttpClient; +import org.jetbrains.annotations.NotNull; import org.junit.Before; import org.junit.Test; import org.web3j.abi.EventEncoder; @@ -43,10 +44,12 @@ import org.web3j.crypto.Credentials; import org.web3j.protocol.core.DefaultBlockParameterName; import org.web3j.protocol.core.methods.request.EthFilter; import org.web3j.protocol.core.methods.response.EthLog; +import org.web3j.protocol.core.methods.response.EthTransaction; import org.web3j.protocol.exceptions.TransactionException; import org.web3j.quorum.enclave.Enclave; import org.web3j.quorum.enclave.Tessera; import org.web3j.quorum.enclave.protocol.EnclaveService; +import org.web3j.quorum.methods.response.PrivatePayload; import org.web3j.quorum.tx.QuorumTransactionManager; import org.web3j.tx.response.PollingTransactionReceiptProcessor; @@ -103,7 +106,7 @@ public class ContainerTests extends ContainerTestBase { goQuorumPollingTransactionReceiptProcessor); // Generate a random value to insert into the log - final String logValue = generateHexString(98765L); + final String logValue = generate64BytesHexString(98765L); // Send the transaction and get the transaction hash final String transactionHash = @@ -131,6 +134,19 @@ public class ContainerTests extends ContainerTestBase { final String codeValueGoQuorum = getCode(goQuorumWeb3j, contractAddress); assertThat(codeValueGoQuorum).startsWith(CONTRACT_PREFIX); assertThat(codeValueBesu).isEqualTo(codeValueGoQuorum); + + // Assert that the private payloads returned are the same + final String enclaveKey = getEnclaveKey(transactionHash); + final PrivatePayload goQuorumPayload = goQuorumWeb3j.quorumGetPrivatePayload(enclaveKey).send(); + final PrivatePayload besuPayload = besuWeb3j.quorumGetPrivatePayload(enclaveKey).send(); + + assertThat(goQuorumPayload.getPrivatePayload()).isEqualTo(besuPayload.getPrivatePayload()); + } + + @NotNull + private String getEnclaveKey(final String transactionHash) throws IOException { + final EthTransaction send = besuWeb3j.ethGetTransactionByHash(transactionHash).send(); + return send.getTransaction().get().getInput(); } @Test @@ -155,7 +171,7 @@ public class ContainerTests extends ContainerTestBase { besuPollingTransactionReceiptProcessor); // Generate a random value to insert into the log - final String logValue = generateHexString(192837L); + final String logValue = generate64BytesHexString(192837L); // Send the transaction and get the transaction hash final String transactionHash = @@ -216,7 +232,7 @@ public class ContainerTests extends ContainerTestBase { ethFilterSubscription.addSingleTopic(eventEncoded); // Generate a value to insert into the log - final String logValue = generateHexString((1234567L)); + final String logValue = generate64BytesHexString(1234567L); final AtomicBoolean checked = new AtomicBoolean(false); final Disposable subscribe = diff --git a/container-tests/tests/src/test/java/org/hyperledger/besu/tests/container/helpers/ContractOperations.java b/container-tests/tests/src/test/java/org/hyperledger/besu/tests/container/helpers/ContractOperations.java index 05274583c3..a247e8ab9f 100644 --- a/container-tests/tests/src/test/java/org/hyperledger/besu/tests/container/helpers/ContractOperations.java +++ b/container-tests/tests/src/test/java/org/hyperledger/besu/tests/container/helpers/ContractOperations.java @@ -21,6 +21,7 @@ import java.math.BigInteger; import java.util.Collections; import java.util.List; +import org.jetbrains.annotations.NotNull; import org.web3j.abi.FunctionEncoder; import org.web3j.crypto.Credentials; import org.web3j.crypto.RawTransaction; @@ -138,13 +139,20 @@ public class ContractOperations { return logReceiptResult.getTransactionHash(); } - public static String generateHexString(final long number) { - final StringBuilder randomValue = new StringBuilder(Long.toHexString(number)); + public static String generate64BytesHexString(final long number) { + final String str = Long.toHexString(number); - while (randomValue.length() < 64) { - randomValue.insert(0, '0'); + return prependZeroesToPadHexStringToGivenLength(str, 64); + } + + @NotNull + public static String prependZeroesToPadHexStringToGivenLength( + final String hexString, final int lenRequested) { + final StringBuilder sb = new StringBuilder(hexString); + while (sb.length() < lenRequested) { + sb.insert(0, '0'); } - return randomValue.toString(); + return sb.toString(); } public static int getNonce(final Quorum quorum, final Credentials credentials) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java index 4c32ce91d2..41deb2db69 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java @@ -43,6 +43,7 @@ public enum RpcMethod { DEBUG_TRACE_TRANSACTION("debug_traceTransaction"), DEBUG_BATCH_RAW_TRANSACTION("debug_batchSendRawTransaction"), DEBUG_GET_BAD_BLOCKS("debug_getBadBlocks"), + GOQUORUM_ETH_GET_QUORUM_PAYLOAD("eth_getQuorumPayload"), GOQUORUM_STORE_RAW("goquorum_storeRaw"), PRIV_CALL("priv_call"), PRIV_GET_PRIVATE_TRANSACTION("priv_getPrivateTransaction"), diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractBlockParameterOrBlockHashMethod.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractBlockParameterOrBlockHashMethod.java index 8a01b0fdfc..d8cad877f5 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractBlockParameterOrBlockHashMethod.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractBlockParameterOrBlockHashMethod.java @@ -72,7 +72,7 @@ public abstract class AbstractBlockParameterOrBlockHashMethod implements JsonRpc } else if (blockParameterOrBlockHash.isPending()) { result = pendingResult(requestContext); } else if (blockParameterOrBlockHash.isNumeric() || blockParameterOrBlockHash.isEarliest()) { - OptionalLong blockNumber = blockParameterOrBlockHash.getNumber(); + final OptionalLong blockNumber = blockParameterOrBlockHash.getNumber(); if (blockNumber.isEmpty() || blockNumber.getAsLong() < 0) { return new JsonRpcErrorResponse( requestContext.getRequest().getId(), JsonRpcError.INVALID_PARAMS); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/GoQuorumEthGetQuorumPayload.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/GoQuorumEthGetQuorumPayload.java new file mode 100644 index 0000000000..e2a5a2c8d1 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/GoQuorumEthGetQuorumPayload.java @@ -0,0 +1,67 @@ +/* + * 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.priv; + +import static org.apache.logging.log4j.LogManager.getLogger; + +import org.hyperledger.besu.enclave.GoQuorumEnclave; +import org.hyperledger.besu.enclave.types.GoQuorumReceiveResponse; +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.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.apache.logging.log4j.Logger; +import org.apache.tuweni.bytes.Bytes; + +public class GoQuorumEthGetQuorumPayload implements JsonRpcMethod { + + private static final Logger LOG = getLogger(); + + private final GoQuorumEnclave enclave; + + public GoQuorumEthGetQuorumPayload(final GoQuorumEnclave enclave) { + this.enclave = enclave; + } + + @Override + public String getName() { + return RpcMethod.GOQUORUM_ETH_GET_QUORUM_PAYLOAD.getMethodName(); + } + + @Override + public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { + final String key = requestContext.getRequiredParameter(0, String.class); + final Bytes bytes; + try { + bytes = Bytes.fromHexString(key); + } catch (final IllegalArgumentException e) { + LOG.debug("Enclave key contains invalid hex character {}", key); + return new JsonRpcErrorResponse( + requestContext.getRequest().getId(), JsonRpcError.INVALID_PARAMS); + } + if (bytes.size() != 64) { + LOG.debug("Enclave key expected length 64, but length is {}", bytes.size()); + return new JsonRpcErrorResponse( + requestContext.getRequest().getId(), JsonRpcError.INVALID_PARAMS); + } + final GoQuorumReceiveResponse receive = enclave.receive(bytes.toBase64String()); + return new JsonRpcSuccessResponse( + requestContext.getRequest().getId(), Bytes.wrap(receive.getPayload()).toHexString()); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/GoQuorumJsonRpcPrivacyMethods.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/GoQuorumJsonRpcPrivacyMethods.java index 9f416a6adf..c33892d27e 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/GoQuorumJsonRpcPrivacyMethods.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/GoQuorumJsonRpcPrivacyMethods.java @@ -14,10 +14,12 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.methods; +import org.hyperledger.besu.enclave.GoQuorumEnclave; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApi; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis; 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.priv.GoQuorumEthGetQuorumPayload; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv.GoQuorumSendRawPrivateTransaction; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv.GoQuorumStoreRawPrivateTransaction; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; @@ -48,11 +50,14 @@ public class GoQuorumJsonRpcPrivacyMethods extends PrivacyApiGroupJsonRpcMethods protected Map create( final PrivacyController privacyController, final EnclavePublicKeyProvider enclavePublicKeyProvider) { + if (goQuorumParameters.isPresent()) { + final GoQuorumEnclave enclave = goQuorumParameters.get().enclave(); return mapOf( new GoQuorumSendRawPrivateTransaction( - goQuorumParameters.get().enclave(), getTransactionPool(), enclavePublicKeyProvider), - new GoQuorumStoreRawPrivateTransaction(goQuorumParameters.get().enclave())); + enclave, getTransactionPool(), enclavePublicKeyProvider), + new GoQuorumStoreRawPrivateTransaction(enclave), + new GoQuorumEthGetQuorumPayload(enclave)); } else { return Collections.emptyMap(); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivacyApiGroupJsonRpcMethods.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivacyApiGroupJsonRpcMethods.java index 16e888c8f6..fadd3e52ca 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivacyApiGroupJsonRpcMethods.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivacyApiGroupJsonRpcMethods.java @@ -152,7 +152,8 @@ public abstract class PrivacyApiGroupJsonRpcMethods extends ApiGroupJsonRpcMetho final PrivacyParameters privacyParameters, final JsonRpcMethod rpcMethod) { final String methodName = rpcMethod.getName(); if (methodName.equals(RpcMethod.ETH_SEND_RAW_PRIVATE_TRANSACTION.getMethodName()) - || methodName.equals(RpcMethod.GOQUORUM_STORE_RAW.getMethodName())) { + || methodName.equals(RpcMethod.GOQUORUM_STORE_RAW.getMethodName()) + || methodName.equals(RpcMethod.GOQUORUM_ETH_GET_QUORUM_PAYLOAD.getMethodName())) { return rpcMethod; } else if (privacyParameters.isEnabled() && privacyParameters.isMultiTenancyEnabled()) { return new MultiTenancyRpcMethodDecorator(rpcMethod); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/goquorum/GoQuorumEthGetQuorumPayloadTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/goquorum/GoQuorumEthGetQuorumPayloadTest.java new file mode 100644 index 0000000000..34fd4baf62 --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/goquorum/GoQuorumEthGetQuorumPayloadTest.java @@ -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.api.jsonrpc.internal.privacy.methods.goquorum; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.enclave.GoQuorumEnclave; +import org.hyperledger.besu.enclave.types.GoQuorumReceiveResponse; +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.priv.GoQuorumEthGetQuorumPayload; +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.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 GoQuorumEthGetQuorumPayloadTest { + + @Mock GoQuorumEthGetQuorumPayload method; + @Mock GoQuorumEnclave enclave; + + @Before + public void before() { + method = new GoQuorumEthGetQuorumPayload(enclave); + } + + @Test + public void correctRequest() { + final String hexString = Bytes.wrap(new byte[64]).toHexString(); + final JsonRpcRequestContext request = + new JsonRpcRequestContext( + new JsonRpcRequest("2.0", "eth_getQuorumPayload", new String[] {hexString})); + + when(enclave.receive(any())) + .thenReturn(new GoQuorumReceiveResponse(new byte[10], 0, null, null)); + + final JsonRpcResponse response = method.response(request); + assertThat(response).isInstanceOf(JsonRpcSuccessResponse.class); + assertThat(((JsonRpcSuccessResponse) response).getResult().toString()) + .isEqualTo("0x00000000000000000000"); + } + + @Test + public void requestIsMissingParameter() { + final JsonRpcRequestContext request = + new JsonRpcRequestContext( + new JsonRpcRequest("2.0", "eth_getQuorumPayload", new String[] {})); + + assertThatThrownBy(() -> method.response(request)) + .isInstanceOf(InvalidJsonRpcParameters.class) + .hasMessage("Missing required json rpc parameter at index 0"); + } + + @Test + public void requestHasNullObjectParameter() { + final JsonRpcRequestContext request = + new JsonRpcRequestContext(new JsonRpcRequest("2.0", "eth_getQuorumPayload", null)); + + assertThatThrownBy(() -> method.response(request)) + .isInstanceOf(InvalidJsonRpcParameters.class) + .hasMessage("Missing required json rpc parameter at index 0"); + } + + @Test + public void requestHasNullArrayParameter() { + final JsonRpcRequestContext request = + new JsonRpcRequestContext( + new JsonRpcRequest("2.0", "eth_getQuorumPayload", new String[] {null})); + + assertThatThrownBy(() -> method.response(request)) + .isInstanceOf(InvalidJsonRpcParameters.class) + .hasMessage("Missing required json rpc parameter at index 0"); + } + + @Test + public void requestHasShortHex() { + final String hexString = Bytes.wrap(new byte[63]).toHexString(); + final JsonRpcRequestContext request = + new JsonRpcRequestContext( + new JsonRpcRequest("2.0", "eth_getQuorumPayload", new String[] {hexString})); + + final JsonRpcResponse response = method.response(request); + assertThat(response).isInstanceOf(JsonRpcErrorResponse.class); + assertThat(response.toString()).contains("INVALID_PARAMS"); + } + + @Test + public void requestHasLongHex() { + final String hexString = Bytes.wrap(new byte[65]).toHexString(); + final JsonRpcRequestContext request = + new JsonRpcRequestContext( + new JsonRpcRequest("2.0", "eth_getQuorumPayload", new String[] {hexString})); + + final JsonRpcResponse response = method.response(request); + assertThat(response).isInstanceOf(JsonRpcErrorResponse.class); + assertThat(response.toString()).contains("INVALID_PARAMS"); + } + + @Test + public void requestNonHexString() { + final String hexString = Bytes.wrap(new byte[63]).toHexString() + "f"; + final JsonRpcRequestContext request = + new JsonRpcRequestContext( + new JsonRpcRequest("2.0", "eth_getQuorumPayload", new String[] {hexString})); + + final JsonRpcResponse response = method.response(request); + assertThat(response).isInstanceOf(JsonRpcErrorResponse.class); + assertThat(response.toString()).contains("INVALID_PARAMS"); + } +}