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 1/2] 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); From cc3b5ce7510841e428fd1869ea8250eb266f1b30 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 8 Jul 2021 15:20:08 +1000 Subject: [PATCH 2/2] QBFT and GoQuorum-compatible privacy release changelog entries QBFT and GoQuorum-compatible privacy release changelog entries Signed-off-by: Jason Frame Signed-off-by: Usman Saleem Co-authored-by: Vijay Michalik --- CHANGELOG.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4437d9bb763..9554b4ed2dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,25 @@ # 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 +- `priv_call` now uses NO_TRACING OperationTracer implementation which improves memory usage [\#2482](https://github.com/hyperledger/besu/pull/2482) ### Bug Fixes +- Ibft2 could create invalid RoundChange messages in some circumstances containing duplicate prepares [\#2449](https://github.com/hyperledger/besu/pull/2449) +- Updated `eth_sendRawTransaction` to return an error when maxPriorityFeePerGas exceeds maxFeePerGas [\#2424](https://github.com/hyperledger/besu/pull/2424) +- Fixed NoSuchElementException with EIP1559 transaction receipts when using eth_getTransactionReceipt [\#2477](https://github.com/hyperledger/besu/pull/2477) + +### Early Access Features +This release contains the activation blocks for London across all supported testnets. They are: + * Ropsten 10_499_401 (24 Jun 2021) + * Goerli 5_062_605 (30 Jun 2021) + * Rinkeby 8_897_988 (7 Jul 2021) +- QBFT is a Byzantine Fault Tolerant consensus algorithm, building on the capabilities of IBFT and IBFT 2.0. It aims to provide performance improvements in cases of excess round change, and provides interoperability with other EEA compliant clients, such as GoQuorum. + - Note: QBFT currently only supports new networks. Existing networks using IBFT2.0 cannot migrate to QBFT. This will become available in a future release. + - Note: QBFT is an early access feature pending community feedback. Please make use of QBFT in new development networks and reach out in case of issues or concerns +- GoQuorum-compatible privacy. This mode uses Tessera and is interoperable with GoQuorum. + - Note: GoQuorum-compatible privacy is an early access feature pending community feedback. ## 21.7.0-RC2