diff --git a/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java b/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java index 8e9598cb664..0bab796ffff 100644 --- a/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java @@ -568,7 +568,9 @@ public Runner build() { Optional graphQLHttpService = Optional.empty(); if (graphQLConfiguration.isEnabled()) { - final GraphQLDataFetchers fetchers = new GraphQLDataFetchers(supportedCapabilities); + final GraphQLDataFetchers fetchers = + new GraphQLDataFetchers( + supportedCapabilities, privacyParameters.getGoQuorumPrivacyParameters()); final GraphQLDataFetcherContextImpl dataFetcherContext = new GraphQLDataFetcherContextImpl( blockchainQueries, diff --git a/build.gradle b/build.gradle index f7ceb6ecf55..a596f01766e 100644 --- a/build.gradle +++ b/build.gradle @@ -663,11 +663,11 @@ task acceptanceTestsQuorum { * Not available features in Besu: privacy-enhancements-disabled, extension, mps * Not available RPC methods in Besu: async, storage-root, get-quorum-payload, personal-api-signed * - * Ignored for now (privacy-polishing): graphql, eth-api-signed, spam, nosupport + * Ignored for now (privacy-polishing): eth-api-signed, spam, nosupport * * LOGGING_LEVEL_COM_QUORUM_GAUGE=DEBUG -- enables HTTP JSON-RPC logging */ - def tags = "(basic && !nosupport && !mps && !spam && !eth-api-signed && !privacy-enhancements-disabled && !graphql && !async && !extension && !storage-root && !get-quorum-payload && !personal-api-signed) || networks/typical-besu::ibft2" + def tags = "(basic && !nosupport && !mps && !spam && !eth-api-signed && !privacy-enhancements-disabled && !async && !extension && !storage-root && !get-quorum-payload && !personal-api-signed) || networks/typical-besu::ibft2" doLast { exec { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLDataFetchers.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLDataFetchers.java index b5b5fa756a5..7f7beb8b779 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLDataFetchers.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLDataFetchers.java @@ -16,6 +16,7 @@ import static com.google.common.base.Preconditions.checkArgument; +import org.hyperledger.besu.enclave.GoQuorumEnclave; import org.hyperledger.besu.ethereum.api.graphql.internal.pojoadapter.AccountAdapter; import org.hyperledger.besu.ethereum.api.graphql.internal.pojoadapter.EmptyAccountAdapter; import org.hyperledger.besu.ethereum.api.graphql.internal.pojoadapter.LogAdapter; @@ -30,6 +31,7 @@ import org.hyperledger.besu.ethereum.api.query.TransactionWithMetadata; import org.hyperledger.besu.ethereum.core.Account; import org.hyperledger.besu.ethereum.core.Address; +import org.hyperledger.besu.ethereum.core.GoQuorumPrivacyParameters; import org.hyperledger.besu.ethereum.core.Hash; import org.hyperledger.besu.ethereum.core.LogTopic; import org.hyperledger.besu.ethereum.core.LogWithMetadata; @@ -56,10 +58,24 @@ import com.google.common.base.Preconditions; import graphql.schema.DataFetcher; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; public class GraphQLDataFetchers { + + private static final Logger LOG = LogManager.getLogger(); + + private Optional goQuorumPrivacyParameters = Optional.empty(); + + public GraphQLDataFetchers( + final Set supportedCapabilities, + final Optional goQuorumPrivacyParameters) { + this(supportedCapabilities); + this.goQuorumPrivacyParameters = goQuorumPrivacyParameters; + } + public GraphQLDataFetchers(final Set supportedCapabilities) { final OptionalInt version = supportedCapabilities.stream() @@ -259,7 +275,46 @@ DataFetcher> getTransactionDataFetcher() { ((GraphQLDataFetcherContext) dataFetchingEnvironment.getContext()).getBlockchainQueries(); final Bytes32 hash = dataFetchingEnvironment.getArgument("hash"); final Optional tran = blockchain.transactionByHash(Hash.wrap(hash)); - return tran.map(TransactionAdapter::new); + return tran.map(this::getTransactionAdapter); }; } + + private TransactionAdapter getTransactionAdapter( + final TransactionWithMetadata transactionWithMetadata) { + final Transaction transaction = transactionWithMetadata.getTransaction(); + return goQuorumPrivacyParameters.isPresent() && transaction.isGoQuorumPrivateTransaction() + ? updatePrivatePayload(transaction) + : new TransactionAdapter(transactionWithMetadata); + } + + private TransactionAdapter updatePrivatePayload(final Transaction transaction) { + final GoQuorumEnclave enclave = goQuorumPrivacyParameters.get().enclave(); + Bytes enclavePayload; + + try { + // Retrieve the payload from the enclave + enclavePayload = + Bytes.wrap(enclave.receive(transaction.getPayload().toBase64String()).getPayload()); + } catch (final Exception ex) { + LOG.debug("An error occurred while retrieving the GoQuorum transaction payload: ", ex); + enclavePayload = Bytes.EMPTY; + } + + // Return a new transaction containing the retrieved payload + return new TransactionAdapter( + new TransactionWithMetadata( + new Transaction( + transaction.getNonce(), + transaction.getGasPrice(), + transaction.getMaxPriorityFeePerGas(), + transaction.getMaxFeePerGas(), + transaction.getGasLimit(), + transaction.getTo(), + transaction.getValue(), + transaction.getSignature(), + enclavePayload, + transaction.getSender(), + transaction.getChainId(), + Optional.ofNullable(transaction.getV())))); + } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/TransactionAdapter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/TransactionAdapter.java index c8af09e318c..64d70387fb8 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/TransactionAdapter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/TransactionAdapter.java @@ -185,4 +185,16 @@ public List getLogs(final DataFetchingEnvironment environment) { } return results; } + + public boolean getIsPrivate() { + return transactionWithMetadata.getTransaction().isGoQuorumPrivateTransaction(); + } + + public Optional getPrivateInputData() { + final Transaction transaction = transactionWithMetadata.getTransaction(); + if (transaction.isGoQuorumPrivateTransaction()) { + return Optional.ofNullable(transaction.getPayload()); + } + return Optional.of(Bytes.EMPTY); + } } 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 index e2a5a2c8d1e..71d6ba700bb 100644 --- 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 @@ -16,6 +16,7 @@ import static org.apache.logging.log4j.LogManager.getLogger; +import org.hyperledger.besu.enclave.EnclaveClientException; import org.hyperledger.besu.enclave.GoQuorumEnclave; import org.hyperledger.besu.enclave.types.GoQuorumReceiveResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; @@ -60,8 +61,20 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { 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()); + + try { + final GoQuorumReceiveResponse receive = enclave.receive(bytes.toBase64String()); + return new JsonRpcSuccessResponse( + requestContext.getRequest().getId(), Bytes.wrap(receive.getPayload()).toHexString()); + } catch (final EnclaveClientException ex) { + if (ex.getStatusCode() == 404) { + return new JsonRpcSuccessResponse( + requestContext.getRequest().getId(), Bytes.EMPTY.toHexString()); + } else { + LOG.debug("Error retrieving enclave payload: ", ex); + return new JsonRpcErrorResponse( + requestContext.getRequest().getId(), JsonRpcError.INTERNAL_ERROR); + } + } } } diff --git a/ethereum/api/src/main/resources/schema.graphqls b/ethereum/api/src/main/resources/schema.graphqls index b065daf5021..49eac5b84ea 100644 --- a/ethereum/api/src/main/resources/schema.graphqls +++ b/ethereum/api/src/main/resources/schema.graphqls @@ -96,6 +96,10 @@ type Transaction { # Logs is a list of log entries emitted by this transaction. If the # transaction has not yet been mined, this field will be null. logs: [Log!] + # IsPrivate is an indicator of a GoQuorum private transaction + isPrivate: Boolean + # PrivateInputData is the actual payload of a GoQuorum private transaction + privateInputData: Bytes } # BlockFilterCriteria encapsulates log filter criteria for a filter applied 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 index 34fd4baf62d..3a1de75fdd4 100644 --- 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 @@ -19,6 +19,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; +import org.hyperledger.besu.enclave.EnclaveClientException; import org.hyperledger.besu.enclave.GoQuorumEnclave; import org.hyperledger.besu.enclave.types.GoQuorumReceiveResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; @@ -130,4 +131,19 @@ public void requestNonHexString() { assertThat(response).isInstanceOf(JsonRpcErrorResponse.class); assertThat(response.toString()).contains("INVALID_PARAMS"); } + + @Test + public void enclave404ReturnsEmptyBytesString() { + 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())).thenThrow(new EnclaveClientException(404, null)); + + final JsonRpcResponse response = method.response(request); + assertThat(response).isInstanceOf(JsonRpcSuccessResponse.class); + assertThat(((JsonRpcSuccessResponse) response).getResult()) + .isEqualTo(Bytes.EMPTY.toHexString()); + } }