From 4a4730c74b4b7692bf0e1064444343d8f8df3243 Mon Sep 17 00:00:00 2001 From: Stefan Pingel <16143240+pinges@users.noreply.github.com> Date: Thu, 8 Jul 2021 14:35:03 +1000 Subject: [PATCH] Cherrypick g cprivacy (#2514) * add eth_getQuorumPayload (#2470) * add eth_getQuorumPayload Signed-off-by: Stefan Pingel * remove unnecessary CLI flag (#2478) * remove unnecessary CLI flag Signed-off-by: Stefan Pingel * no trace for method calls in private tx simulator (#2482) * no trace for method calls in private tx simulator Signed-off-by: Sally MacFarlane * GraphQL schema + adapter updates to handle GoQuorum transaction fields (#2490) * Added graphQL schema update and associated Adapter methods. Signed-off-by: Mark Terry Signed-off-by: Stefan Pingel * let acceptanceTestsQuorum fail the loop Signed-off-by: Stefan Pingel Co-authored-by: Sally MacFarlane Co-authored-by: mark-terry <36909937+mark-terry@users.noreply.github.com> --- CHANGELOG.md | 5 + .../plugins/PermissioningPluginTest.java | 6 +- .../org/hyperledger/besu/RunnerBuilder.java | 4 +- .../org/hyperledger/besu/cli/BesuCommand.java | 67 +++----- .../besu/cli/util/CommandLineUtils.java | 15 +- .../hyperledger/besu/cli/BesuCommandTest.java | 120 +++----------- .../besu/cli/CommandLineUtilsTest.java | 34 ++-- build.gradle | 4 +- .../tests/container/ContainerTestBase.java | 1 - .../besu/tests/container/ContainerTests.java | 24 ++- .../container/helpers/ContractOperations.java | 20 ++- .../api/graphql/GraphQLDataFetchers.java | 57 ++++++- .../pojoadapter/TransactionAdapter.java | 12 ++ .../besu/ethereum/api/jsonrpc/RpcMethod.java | 1 + ...stractBlockParameterOrBlockHashMethod.java | 2 +- .../priv/GoQuorumEthGetQuorumPayload.java | 80 ++++++++++ .../GoQuorumJsonRpcPrivacyMethods.java | 9 +- .../PrivacyApiGroupJsonRpcMethods.java | 3 +- .../api/src/main/resources/schema.graphqls | 4 + .../GoQuorumEthGetQuorumPayloadTest.java | 149 ++++++++++++++++++ .../OnChainPrivacyPrecompiledContract.java | 5 +- .../privacy/PrivacyPrecompiledContract.java | 5 +- .../privacy/PrivateTransactionSimulator.java | 5 +- 23 files changed, 436 insertions(+), 196 deletions(-) create mode 100644 ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/GoQuorumEthGetQuorumPayload.java create mode 100644 ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/goquorum/GoQuorumEthGetQuorumPayloadTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index a7ff9191ea8..4437d9bb763 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 21.7.1 + +### Additions and Improvements +- `priv_call` now uses NO_TRACING OperationTracer implementation which improves memory usage [\#2482](https://github.com/hyperledger/besu/pull/2482) + ## 21.7.0 ### Additions and Improvements diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/PermissioningPluginTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/PermissioningPluginTest.java index 65b78b130f2..459849e30dc 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/PermissioningPluginTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/PermissioningPluginTest.java @@ -37,7 +37,7 @@ public class PermissioningPluginTest extends AcceptanceTestBase { @Before public void setUp() throws Exception { - BesuNodeConfigurationBuilder builder = + final BesuNodeConfigurationBuilder builder = new BesuNodeConfigurationBuilder() .miningEnabled(false) .plugins(Collections.singletonList("testPlugins")) @@ -86,9 +86,9 @@ public void transactionsAreNotSendToBlockPendingTransactionsNode() { final Account account = accounts.createAccount("account-one"); final Amount balance = Amount.ether(20); - TransferTransaction tx = accountTransactions.createTransfer(account, balance); + final TransferTransaction tx = accountTransactions.createTransfer(account, balance); - Hash txHash = aliceNode.execute(tx); + final Hash txHash = aliceNode.execute(tx); aliceNode.verify(txPoolConditions.inTransactionPool(txHash)); bobNode.verify(txPoolConditions.inTransactionPool(txHash)); 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/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java index d2bd35ce63c..acde2643fcf 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -393,6 +393,8 @@ public class BesuCommand implements DefaultCommandValues, Runnable { "Allow for incoming connections to be prioritized randomly. This will prevent (typically small, stable) networks from forming impenetrable peer cliques. (default: ${DEFAULT-VALUE})") private final Boolean randomPeerPriority = false; + private GenesisConfigOptions genesisConfigOptions; + @Option( names = {"--banned-node-ids", "--banned-node-id"}, paramLabel = MANDATORY_NODE_ID_FORMAT_HELP, @@ -1080,12 +1082,6 @@ void setBannedNodeIds(final List values) { description = "Maximum gas price for eth_gasPrice (default: ${DEFAULT-VALUE})") private final Long apiGasPriceMax = 500_000_000_000L; - @Option( - names = {"--goquorum-compatibility-enabled"}, - hidden = true, - description = "Start Besu in GoQuorum compatibility mode (default: ${DEFAULT-VALUE})") - private final Boolean isGoQuorumCompatibilityMode = false; - @CommandLine.Option( names = {"--static-nodes-file"}, paramLabel = MANDATORY_FILE_FORMAT_HELP, @@ -1114,6 +1110,7 @@ void setBannedNodeIds(final List values) { private Vertx vertx; private EnodeDnsConfiguration enodeDnsConfiguration; private KeyValueStorageProvider keyValueStorageProvider; + private Boolean isGoQuorumCompatibilityMode = false; public BesuCommand( final Logger logger, @@ -1190,6 +1187,17 @@ public void run() { try { configureLogging(true); + + // Set the goquorum compatibility mode based on the genesis file + if (genesisFile != null) { + genesisConfigOptions = readGenesisConfigOptions(); + if (genesisConfigOptions.isQuorum()) { + // this static flag is read by the RLP decoder + GoQuorumOptions.goQuorumCompatibilityMode = true; + isGoQuorumCompatibilityMode = true; + } + } + instantiateSignatureAlgorithmFactory(); configureNativeLibs(); logger.info("Starting Besu version: {}", BesuInfo.nodeName(identityString)); @@ -1533,7 +1541,7 @@ private void issueOptionWarnings() { CommandLineUtils.checkMultiOptionDependencies( logger, commandLine, - List.of("--miner-enabled", "--goquorum-compatibility-enabled"), + "--min-gas-price ignored because none of --miner-enabled or isQuorum (in genesis file) was defined.", List.of(!isMiningEnabled, !isGoQuorumCompatibilityMode), singletonList("--min-gas-price")); @@ -1564,7 +1572,6 @@ private void configure() throws Exception { ethNetworkConfig = updateNetworkConfig(getNetwork()); - checkGoQuorumGenesisConfig(); checkGoQuorumCompatibilityConfig(ethNetworkConfig); jsonRpcConfiguration = jsonRpcConfiguration(); @@ -1626,7 +1633,7 @@ private String readEnclaveKey() { } catch (final Exception e) { throw new ParameterException( this.commandLine, - "--privacy-public-key-file must be set when --goquorum-compatibility-enabled is set to true.", + "--privacy-public-key-file must be set if isQuorum is set in the genesis file.", e); } if (key.length() != 44) { @@ -2050,7 +2057,6 @@ private Optional quorumPermissioningConfig() } try { - final GenesisConfigOptions genesisConfigOptions = readGenesisConfigOptions(); final OptionalLong qip714BlockNumber = genesisConfigOptions.getQip714BlockNumber(); return Optional.of( GoQuorumPermissioningConfiguration.enabled( @@ -2080,7 +2086,7 @@ private PrivacyParameters privacyParameters(final KeyValueStorageProvider storag CommandLineUtils.checkMultiOptionDependencies( logger, commandLine, - List.of("--privacy-enabled", "--goquorum-compatibility-enabled"), + "--privacy-url and/or --privacy-public-key-file ignored because none of --privacy-enabled or isQuorum (in genesis file) was defined.", List.of(!isPrivacyEnabled, !isGoQuorumCompatibilityMode), List.of("--privacy-url", "--privacy-public-key-file")); @@ -2601,42 +2607,16 @@ private void addPortIfEnabled( } } - private void checkGoQuorumGenesisConfig() { - if (genesisFile != null) { - if (readGenesisConfigOptions().isQuorum() && !isGoQuorumCompatibilityMode) { - throw new IllegalStateException( - "Cannot use GoQuorum genesis file without GoQuorum privacy enabled"); - } - } - } - private void checkGoQuorumCompatibilityConfig(final EthNetworkConfig ethNetworkConfig) { if (isGoQuorumCompatibilityMode) { - if (genesisFile == null) { - throw new ParameterException( - this.commandLine, - "--genesis-file must be specified if GoQuorum compatibility mode is enabled."); - } - - final GenesisConfigOptions genesisConfigOptions = readGenesisConfigOptions(); - - // this static flag is read by the RLP decoder - GoQuorumOptions.goQuorumCompatibilityMode = true; - - if (!genesisConfigOptions.isQuorum()) { - throw new IllegalStateException( - "GoQuorum compatibility mode (enabled) can only be used if genesis file has 'isQuorum' flag set to true."); - } - if (!minTransactionGasPrice.isZero()) { throw new ParameterException( this.commandLine, - "--min-gas-price must be set to zero if GoQuorum compatibility is enabled in the genesis config."); + "--min-gas-price must be set to zero if isQuorum mode is enabled in the genesis file."); } if (ensureGoQuorumCompatibilityModeNotUsedOnMainnet(genesisConfigOptions, ethNetworkConfig)) { - throw new ParameterException( - this.commandLine, "GoQuorum compatibility mode (enabled) cannot be used on Mainnet."); + throw new ParameterException(this.commandLine, "isQuorum mode cannot be used on Mainnet."); } } } @@ -2681,7 +2661,7 @@ private void instantiateSignatureAlgorithmFactory() { return; } - Optional ecCurve = getEcCurveFromGenesisFile(); + final Optional ecCurve = getEcCurveFromGenesisFile(); if (ecCurve.isEmpty()) { SignatureAlgorithmFactory.setDefaultInstance(); @@ -2690,7 +2670,7 @@ private void instantiateSignatureAlgorithmFactory() { try { SignatureAlgorithmFactory.setInstance(SignatureAlgorithmType.create(ecCurve.get())); - } catch (IllegalArgumentException e) { + } catch (final IllegalArgumentException e) { throw new CommandLine.InitializationException( new StringBuilder() .append("Invalid genesis file configuration for ecCurve. ") @@ -2703,9 +2683,6 @@ private Optional getEcCurveFromGenesisFile() { if (genesisFile == null) { return Optional.empty(); } - - GenesisConfigOptions options = readGenesisConfigOptions(); - - return options.getEcCurve(); + return genesisConfigOptions.getEcCurve(); } } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/util/CommandLineUtils.java b/besu/src/main/java/org/hyperledger/besu/cli/util/CommandLineUtils.java index b2a63fb62d3..388466893ea 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/util/CommandLineUtils.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/util/CommandLineUtils.java @@ -70,28 +70,25 @@ public static void checkOptionDependencies( * that could replace this. See https://github.com/remkop/picocli/issues/295 * * @param logger the logger instance used to log the warning - * @param commandLine the command line containing the options we want to check - * @param mainOptions the names of the main options to test dependency against. Only used for - * display. + * @param commandLine the command line containing the options we want to check display. + * @param stringToLog the string that is going to be logged. * @param isMainOptionCondition the conditions to test dependent options against. If all * conditions are true, dependent options will be checked. * @param dependentOptionsNames a list of option names that can't be used if condition is met. - * Example: if --min-gas-price is in the list and condition is that either --miner-enabled or - * --goquorum-compatibility-enabled should not be false, we log a warning. + * Example: if --min-gas-price is in the list and condition is that --miner-enabled should not + * be false, we log a warning. */ public static void checkMultiOptionDependencies( final Logger logger, final CommandLine commandLine, - final List mainOptions, + final String stringToLog, final List isMainOptionCondition, final List dependentOptionsNames) { if (isMainOptionCondition.stream().allMatch(isTrue -> isTrue)) { final String affectedOptions = getAffectedOptions(commandLine, dependentOptionsNames); if (!affectedOptions.isEmpty()) { - final String joinedMainOptions = - StringUtils.joiningWithLastDelimiter(", ", " or ").apply(mainOptions); - logger.warn(MULTI_DEPENDENCY_WARNING_MSG, affectedOptions, joinedMainOptions); + logger.warn(stringToLog); } } } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java index f27d535edaa..f78b67453ce 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java @@ -26,7 +26,6 @@ import static org.hyperledger.besu.cli.config.NetworkName.RINKEBY; import static org.hyperledger.besu.cli.config.NetworkName.ROPSTEN; import static org.hyperledger.besu.cli.util.CommandLineUtils.DEPENDENCY_WARNING_MSG; -import static org.hyperledger.besu.cli.util.CommandLineUtils.MULTI_DEPENDENCY_WARNING_MSG; import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.ETH; import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.NET; import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.PERM; @@ -76,7 +75,6 @@ import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; import org.hyperledger.besu.nat.NatMethod; import org.hyperledger.besu.plugin.data.EnodeURL; -import org.hyperledger.besu.util.StringUtils; import org.hyperledger.besu.util.number.Fraction; import org.hyperledger.besu.util.number.Percentage; @@ -134,9 +132,9 @@ public class BesuCommandTest extends CommandTestAbstract { (new JsonObject()) .put( "config", - new JsonObject().put("isquorum", true).put("chainId", GENESIS_CONFIG_TEST_CHAINID)); + new JsonObject().put("isQuorum", true).put("chainId", GENESIS_CONFIG_TEST_CHAINID)); private static final JsonObject INVALID_GENESIS_QUORUM_INTEROP_ENABLED_MAINNET = - (new JsonObject()).put("config", new JsonObject().put("isquorum", true)); + (new JsonObject()).put("config", new JsonObject().put("isQuorum", true)); private static final JsonObject INVALID_GENESIS_EC_CURVE = (new JsonObject()).put("config", new JsonObject().put("ecCurve", "abcd")); private static final JsonObject VALID_GENESIS_EC_CURVE = @@ -1618,7 +1616,8 @@ public void launcherDefaultOptionValue() { @Test public void launcherOptionIsParsedCorrectly() { - TestBesuCommand besuCommand = parseCommand("--Xlauncher", "true", "--Xlauncher-force", "true"); + final TestBesuCommand besuCommand = + parseCommand("--Xlauncher", "true", "--Xlauncher-force", "true"); assertThat(besuCommand.getLauncherOptions().isLauncherMode()).isTrue(); assertThat(besuCommand.getEnodeDnsConfiguration().updateEnabled()).isFalse(); @@ -1974,7 +1973,7 @@ public void rpcHttpHostMayBeIPv6() { @Test public void rpcHttpMaxActiveConnectionsPropertyMustBeUsed() { - int maxConnections = 99; + final int maxConnections = 99; parseCommand("--rpc-http-max-active-connections", String.valueOf(maxConnections)); verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); @@ -1989,7 +1988,7 @@ public void rpcHttpMaxActiveConnectionsPropertyMustBeUsed() { @Test public void rpcWsMaxActiveConnectionsPropertyMustBeUsed() { - int maxConnections = 99; + final int maxConnections = 99; parseCommand("--rpc-ws-max-active-connections", String.valueOf(maxConnections)); verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); @@ -3004,14 +3003,14 @@ public void minGasPriceRequiresMainOption() { parseCommand("--min-gas-price", "0"); verifyMultiOptionsConstraintLoggerCall( - List.of("--miner-enabled", "--goquorum-compatibility-enabled"), "--min-gas-price"); + "--min-gas-price ignored because none of --miner-enabled or isQuorum (in genesis file) was defined."); assertThat(commandOutput.toString()).isEmpty(); assertThat(commandErrorOutput.toString()).isEmpty(); } @Test - public void miningParametersAreCaptured() throws Exception { + public void miningParametersAreCaptured() { final Address requestedCoinbase = Address.fromHexString("0000011111222223333344444"); final String extraDataString = "0x1122334455667788990011223344556677889900112233445566778899001122"; @@ -3336,9 +3335,7 @@ public void privacyOptionsRequiresServiceToBeEnabled() { parseCommand("--privacy-url", ENCLAVE_URI, "--privacy-public-key-file", file.toString()); verifyMultiOptionsConstraintLoggerCall( - List.of("--privacy-enabled", "--goquorum-compatibility-enabled"), - "--privacy-url", - "--privacy-public-key-file"); + "--privacy-url and/or --privacy-public-key-file ignored because none of --privacy-enabled or isQuorum (in genesis file) was defined."); assertThat(commandOutput.toString()).isEmpty(); assertThat(commandErrorOutput.toString()).isEmpty(); @@ -3552,25 +3549,10 @@ private void verifyOptionsConstraintLoggerCall( *

Here we check the calls to logger and not the result of the log line as we don't test the * logger itself but the fact that we call it. * - * @param dependentOptions the string representing the list of dependent options names - * @param mainOptions the main option names + * @param stringToLog the string that is logged */ - private void verifyMultiOptionsConstraintLoggerCall( - final List mainOptions, final String... dependentOptions) { - verify(mockLogger, atLeast(1)) - .warn( - stringArgumentCaptor.capture(), - stringArgumentCaptor.capture(), - stringArgumentCaptor.capture()); - assertThat(stringArgumentCaptor.getAllValues().get(0)).isEqualTo(MULTI_DEPENDENCY_WARNING_MSG); - - for (final String option : dependentOptions) { - assertThat(stringArgumentCaptor.getAllValues().get(1)).contains(option); - } - - final String joinedOptions = - StringUtils.joiningWithLastDelimiter(", ", " or ").apply(mainOptions); - assertThat(stringArgumentCaptor.getAllValues().get(2)).isEqualTo(joinedOptions); + private void verifyMultiOptionsConstraintLoggerCall(final String stringToLog) { + verify(mockLogger, atLeast(1)).warn(stringToLog); } @Test @@ -3594,29 +3576,13 @@ public void privacyWithGoQuorumModeMustError() throws IOException { final Path genesisFile = createFakeGenesisFile(VALID_GENESIS_QUORUM_INTEROP_ENABLED_WITH_CHAINID); parseCommand( - "--goquorum-compatibility-enabled", - "--privacy-enabled", - "--genesis-file", - genesisFile.toString(), - "--min-gas-price", - "0"); + "--privacy-enabled", "--genesis-file", genesisFile.toString(), "--min-gas-price", "0"); assertThat(commandErrorOutput.toString()) .contains("GoQuorum mode cannot be enabled with privacy."); assertThat(commandOutput.toString()).isEmpty(); } - @Test - public void goQuorumGenesisFileWithoutGoQuorumCompatibilityMustError() throws IOException { - final Path genesisFile = - createFakeGenesisFile(VALID_GENESIS_QUORUM_INTEROP_ENABLED_WITH_CHAINID); - parseCommand("--genesis-file", genesisFile.toString()); - - assertThat(commandErrorOutput.toString()) - .contains("Cannot use GoQuorum genesis file without GoQuorum privacy enabled"); - assertThat(commandOutput.toString()).isEmpty(); - } - @Rule public TemporaryFolder testFolder = new TemporaryFolder(); @Test @@ -4151,10 +4117,9 @@ public void assertThatCompatibilityEth64ForkIdIsPresentInHelpMessage() { } @Test - public void quorumInteropDisabledDoesNotEnforceZeroGasPrice() throws IOException { + public void quorumInteropNotDefinedInGenesisDoesNotEnforceZeroGasPrice() throws IOException { final Path genesisFile = createFakeGenesisFile(GENESIS_VALID_JSON); - parseCommand( - "--goquorum-compatibility-enabled=false", "--genesis-file", genesisFile.toString()); + parseCommand("--genesis-file", genesisFile.toString()); assertThat(commandErrorOutput.toString()).isEmpty(); } @@ -4162,25 +4127,20 @@ public void quorumInteropDisabledDoesNotEnforceZeroGasPrice() throws IOException public void quorumInteropEnabledFailsWithoutGasPriceSet() throws IOException { final Path genesisFile = createFakeGenesisFile(VALID_GENESIS_QUORUM_INTEROP_ENABLED_WITH_CHAINID); - parseCommand("--goquorum-compatibility-enabled", "--genesis-file", genesisFile.toString()); + parseCommand("--genesis-file", genesisFile.toString()); assertThat(commandErrorOutput.toString()) .contains( - "--min-gas-price must be set to zero if GoQuorum compatibility is enabled in the genesis config."); + "--min-gas-price must be set to zero if isQuorum mode is enabled in the genesis file."); } @Test public void quorumInteropEnabledFailsWithoutGasPriceSetToZero() throws IOException { final Path genesisFile = createFakeGenesisFile(VALID_GENESIS_QUORUM_INTEROP_ENABLED_WITH_CHAINID); - parseCommand( - "--goquorum-compatibility-enabled", - "--genesis-file", - genesisFile.toString(), - "--min-gas-price", - "1"); + parseCommand("--genesis-file", genesisFile.toString(), "--min-gas-price", "1"); assertThat(commandErrorOutput.toString()) .contains( - "--min-gas-price must be set to zero if GoQuorum compatibility is enabled in the genesis config."); + "--min-gas-price must be set to zero if isQuorum mode is enabled in the genesis file."); } @Test @@ -4188,7 +4148,6 @@ public void quorumInteropEnabledSucceedsWithGasPriceSetToZero() throws IOExcepti final Path genesisFile = createFakeGenesisFile(VALID_GENESIS_QUORUM_INTEROP_ENABLED_WITH_CHAINID); parseCommand( - "--goquorum-compatibility-enabled", "--genesis-file", genesisFile.toString(), "--min-gas-price", @@ -4203,7 +4162,6 @@ public void quorumInteropEnabledFailsIfEnclaveKeyFileDoesNotExist() throws IOExc final Path genesisFile = createFakeGenesisFile(VALID_GENESIS_QUORUM_INTEROP_ENABLED_WITH_CHAINID); parseCommand( - "--goquorum-compatibility-enabled", "--genesis-file", genesisFile.toString(), "--min-gas-price", @@ -4211,57 +4169,31 @@ public void quorumInteropEnabledFailsIfEnclaveKeyFileDoesNotExist() throws IOExc "--privacy-public-key-file", "ThisFileDoesNotExist"); assertThat(commandErrorOutput.toString()) - .contains( - "--privacy-public-key-file must be set when --goquorum-compatibility-enabled is set to true."); + .contains("--privacy-public-key-file must be set if isQuorum is set in the genesis file."); } @Test public void quorumInteropEnabledFailsIfEnclaveKeyFileIsNotSet() throws IOException { final Path genesisFile = createFakeGenesisFile(VALID_GENESIS_QUORUM_INTEROP_ENABLED_WITH_CHAINID); - parseCommand( - "--goquorum-compatibility-enabled", - "--genesis-file", - genesisFile.toString(), - "--min-gas-price", - "0"); - assertThat(commandErrorOutput.toString()) - .contains( - "--privacy-public-key-file must be set when --goquorum-compatibility-enabled is set to true."); - } - - @Test - public void quorumInteropEnabledFailsIfGenesisFileNotSet() { - parseCommand("--goquorum-compatibility-enabled"); + parseCommand("--genesis-file", genesisFile.toString(), "--min-gas-price", "0"); assertThat(commandErrorOutput.toString()) - .contains("--genesis-file must be specified if GoQuorum compatibility mode is enabled."); + .contains("--privacy-public-key-file must be set if isQuorum is set in the genesis file."); } @Test public void quorumInteropEnabledFailsWithMainnetDefaultNetwork() throws IOException { final Path genesisFile = createFakeGenesisFile(INVALID_GENESIS_QUORUM_INTEROP_ENABLED_MAINNET); - parseCommand( - "--goquorum-compatibility-enabled", - "--genesis-file", - genesisFile.toString(), - "--min-gas-price", - "0"); - assertThat(commandErrorOutput.toString()) - .contains("GoQuorum compatibility mode (enabled) cannot be used on Mainnet"); + parseCommand("--genesis-file", genesisFile.toString(), "--min-gas-price", "0"); + assertThat(commandErrorOutput.toString()).contains("isQuorum mode cannot be used on Mainnet."); } @Test public void quorumInteropEnabledFailsWithMainnetChainId() throws IOException { final Path genesisFile = createFakeGenesisFile(INVALID_GENESIS_QUORUM_INTEROP_ENABLED_MAINNET.put("chainId", "1")); - parseCommand( - "--goquorum-compatibility-enabled", - "--genesis-file", - genesisFile.toString(), - "--min-gas-price", - "0"); - assertThat(commandErrorOutput.toString()) - .contains("GoQuorum compatibility mode (enabled) cannot be used on Mainnet"); + parseCommand("--genesis-file", genesisFile.toString(), "--min-gas-price", "0"); + assertThat(commandErrorOutput.toString()).contains("isQuorum mode cannot be used on Mainnet."); } @Test diff --git a/besu/src/test/java/org/hyperledger/besu/cli/CommandLineUtilsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/CommandLineUtilsTest.java index dd2e5953c29..9a2cb30a8ae 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/CommandLineUtilsTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/CommandLineUtilsTest.java @@ -16,7 +16,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.cli.util.CommandLineUtils.DEPENDENCY_WARNING_MSG; -import static org.hyperledger.besu.cli.util.CommandLineUtils.MULTI_DEPENDENCY_WARNING_MSG; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static picocli.CommandLine.defaultExceptionHandler; @@ -122,7 +121,7 @@ public void run() { CommandLineUtils.checkMultiOptionDependencies( logger, commandLine, - List.of("--option-enabled", "--other-option-enabled"), + "--option2 and/or --option3 ignored because none of --option-enabled or --other-option-enabled was defined.", List.of(!optionEnabled, !otherOptionEnabled), Arrays.asList("--option2", "--option3")); } @@ -223,7 +222,8 @@ public void multipleMainOptions() { "--option2", "20"); verifyMultiOptionsConstraintLoggerCall( - mockLogger, "--option2", "--option-enabled", "--other-option-enabled"); + mockLogger, + "--option2 and/or --option3 ignored because none of --option-enabled or --other-option-enabled was defined."); assertThat(testCommand.optionEnabled).isFalse(); assertThat(testCommand.otherOptionEnabled).isFalse(); @@ -244,20 +244,6 @@ private void verifyOptionsConstraintLoggerCall( verifyCall(logger, dependentOptions, DEPENDENCY_WARNING_MSG, mainOptions); } - /** - * Check logger calls, where multiple main options have been specified - * - *

Here we check the calls to logger and not the result of the log line as we don't test the - * logger itself but the fact that we call it. - * - * @param dependentOptions the string representing the list of dependent options names - * @param mainOptions the main option name - */ - private void verifyMultiOptionsConstraintLoggerCall( - final Logger logger, final String dependentOptions, final String... mainOptions) { - verifyCall(logger, dependentOptions, MULTI_DEPENDENCY_WARNING_MSG, mainOptions); - } - private void verifyCall( final Logger logger, final String dependentOptions, @@ -278,4 +264,18 @@ private void verifyCall( StringUtils.joiningWithLastDelimiter(", ", " or ").apply(Arrays.asList(mainOptions)); assertThat(stringArgumentCaptor.getAllValues().get(2)).isEqualTo(joinedMainOptions); } + + /** + * Check logger calls, where multiple main options have been specified + * + *

Here we check the calls to logger and not the result of the log line as we don't test the + * logger itself but the fact that we call it. + * + * @param stringToLog the string representing the list of dependent options names + */ + private void verifyMultiOptionsConstraintLoggerCall( + final Logger logger, final String stringToLog) { + + verify(logger).warn(stringToLog); + } } 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/container-tests/tests/src/test/java/org/hyperledger/besu/tests/container/ContainerTestBase.java b/container-tests/tests/src/test/java/org/hyperledger/besu/tests/container/ContainerTestBase.java index 4a2907922a7..1cefbf95e02 100644 --- a/container-tests/tests/src/test/java/org/hyperledger/besu/tests/container/ContainerTestBase.java +++ b/container-tests/tests/src/test/java/org/hyperledger/besu/tests/container/ContainerTestBase.java @@ -173,7 +173,6 @@ private GenericContainer buildBesuContainer() { besuRpcPort.toString(), "--rpc-http-api", "ADMIN,ETH,NET,WEB3,GOQUORUM", - "--goquorum-compatibility-enabled", "--min-gas-price", "0", "--privacy-public-key-file", 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 5a6051ae74f..717025b093c 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.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 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.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 void contractShouldBeDeployedToBothNodes() throws IOException, Transactio 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 void contractShouldBeDeployedToBothNodes() throws IOException, Transactio 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 void contractShouldBeDeployedOnlyToGoQuorumNode() 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 void contractShouldBeDeployedOnlyToBesuNode() 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 05274583c3b..1abc5ec9069 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.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; @@ -83,7 +84,7 @@ public static String deployContractAndReturnAddress( public static String getCode(final Quorum web3j, final String contractAddress) throws IOException { - DefaultBlockParameter blockParam = + final DefaultBlockParameter blockParam = DefaultBlockParameter.valueOf(DefaultBlockParameterName.LATEST.toString()); final EthGetCode codeResult = web3j.ethGetCode(contractAddress, blockParam).send(); assertThat(codeResult.getCode()) @@ -138,13 +139,20 @@ public static String sendLogEventAndReturnTransactionHash( 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/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/RpcMethod.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java index 4c32ce91d25..41deb2db695 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 8a01b0fdfc8..d8cad877f57 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 @@ protected Object handleParamTypes(final JsonRpcRequestContext requestContext) { } 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 00000000000..71d6ba700bb --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/GoQuorumEthGetQuorumPayload.java @@ -0,0 +1,80 @@ +/* + * 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.EnclaveClientException; +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); + } + + 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/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 9f416a6adfb..c33892d27e4 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 GoQuorumJsonRpcPrivacyMethods( 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 16e888c8f6b..fadd3e52ca9 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 @@ private JsonRpcMethod createPrivacyMethod( 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/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 new file mode 100644 index 00000000000..3a1de75fdd4 --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/goquorum/GoQuorumEthGetQuorumPayloadTest.java @@ -0,0 +1,149 @@ +/* + * 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.EnclaveClientException; +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"); + } + + @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()); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/OnChainPrivacyPrecompiledContract.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/OnChainPrivacyPrecompiledContract.java index df3d866fdd1..face2d6d356 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/OnChainPrivacyPrecompiledContract.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/OnChainPrivacyPrecompiledContract.java @@ -32,7 +32,6 @@ import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.core.Wei; import org.hyperledger.besu.ethereum.core.WorldUpdater; -import org.hyperledger.besu.ethereum.debug.TraceOptions; import org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver; import org.hyperledger.besu.ethereum.privacy.PrivateTransaction; import org.hyperledger.besu.ethereum.privacy.PrivateTransactionEvent; @@ -46,9 +45,9 @@ import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.ethereum.rlp.RLPInput; -import org.hyperledger.besu.ethereum.vm.DebugOperationTracer; import org.hyperledger.besu.ethereum.vm.GasCalculator; import org.hyperledger.besu.ethereum.vm.MessageFrame; +import org.hyperledger.besu.ethereum.vm.OperationTracer; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.util.Subscribers; @@ -421,7 +420,7 @@ protected TransactionProcessingResult simulateTransaction( messageFrame.getTransactionHash(), buildSimulationTransaction(privacyGroupId, privateWorldStateUpdater, methodSignature), messageFrame.getMiningBeneficiary(), - new DebugOperationTracer(TraceOptions.DEFAULT), + OperationTracer.NO_TRACING, messageFrame.getBlockHashLookup(), privacyGroupId); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java index 75b3677a7f2..344066f51a1 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java @@ -26,7 +26,6 @@ import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.core.WorldUpdater; -import org.hyperledger.besu.ethereum.debug.TraceOptions; import org.hyperledger.besu.ethereum.mainnet.AbstractPrecompiledContract; import org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver; import org.hyperledger.besu.ethereum.privacy.PrivateTransaction; @@ -36,9 +35,9 @@ import org.hyperledger.besu.ethereum.privacy.storage.PrivateTransactionMetadata; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; -import org.hyperledger.besu.ethereum.vm.DebugOperationTracer; import org.hyperledger.besu.ethereum.vm.GasCalculator; import org.hyperledger.besu.ethereum.vm.MessageFrame; +import org.hyperledger.besu.ethereum.vm.OperationTracer; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import java.util.Base64; @@ -224,7 +223,7 @@ TransactionProcessingResult processPrivateTransaction( messageFrame.getTransactionHash(), privateTransaction, messageFrame.getMiningBeneficiary(), - new DebugOperationTracer(TraceOptions.DEFAULT), + OperationTracer.NO_TRACING, messageFrame.getBlockHashLookup(), privacyGroupId); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionSimulator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionSimulator.java index f3c139325eb..77ad089018e 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionSimulator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionSimulator.java @@ -25,13 +25,12 @@ import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.Wei; -import org.hyperledger.besu.ethereum.debug.TraceOptions; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.transaction.CallParameter; import org.hyperledger.besu.ethereum.vm.BlockHashLookup; -import org.hyperledger.besu.ethereum.vm.DebugOperationTracer; +import org.hyperledger.besu.ethereum.vm.OperationTracer; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import java.util.Optional; @@ -140,7 +139,7 @@ private Optional process( // exist transaction, protocolSpec.getMiningBeneficiaryCalculator().calculateBeneficiary(header), - new DebugOperationTracer(TraceOptions.DEFAULT), + OperationTracer.NO_TRACING, new BlockHashLookup(header, blockchain), privacyGroupId);