diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d29b8f8af8..1cbfa4cbd3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ - Promote experimental `besu storage x-trie-log` subcommand to production-ready [#7278](https://github.com/hyperledger/besu/pull/7278) - Enhanced BFT round-change diagnostics [#7271](https://github.com/hyperledger/besu/pull/7271) - `--Xsnapsync-bft-enabled` option enables experimental support for snap sync with IBFT/QBFT permissioned Bonsai-DB chains [#7140](https://github.com/hyperledger/besu/pull/7140) +- `privacy-nonce-always-increments` option enables private transactions to always increment the nonce, even if the transaction is invalid [#6593](https://github.com/hyperledger/besu/pull/6593) ### Bug fixes - Validation errors ignored in accounts-allowlist and empty list [#7138](https://github.com/hyperledger/besu/issues/7138) diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java index 583992718aa..37906761d1a 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java @@ -166,6 +166,9 @@ public void startNode(final BesuNode node) { if (node.getPrivacyParameters().isPrivacyPluginEnabled()) { params.add("--Xprivacy-plugin-enabled"); } + if (node.getPrivacyParameters().isPrivateNonceAlwaysIncrementsEnabled()) { + params.add("privacy-nonce-always-increments"); + } } if (!node.getBootnodes().isEmpty()) { diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java index 96ab9cf6235..1ea29388bd5 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java @@ -287,7 +287,8 @@ public BesuNode createNodeWithMultiTenantedPrivacy( final String enclaveUrl, final String authFile, final String privTransactionSigningKey, - final boolean enableFlexiblePrivacy) + final boolean enableFlexiblePrivacy, + final boolean enablePrivateNonceAlwaysIncrements) throws IOException, URISyntaxException { final PrivacyParameters.Builder privacyParametersBuilder = new PrivacyParameters.Builder(); final PrivacyParameters privacyParameters = @@ -298,6 +299,7 @@ public BesuNode createNodeWithMultiTenantedPrivacy( .setStorageProvider(new InMemoryPrivacyStorageProvider()) .setEnclaveFactory(new EnclaveFactory(Vertx.vertx())) .setEnclaveUrl(URI.create(enclaveUrl)) + .setPrivateNonceAlwaysIncrementsEnabled(enablePrivateNonceAlwaysIncrements) .setPrivateKeyPath( Paths.get(ClassLoader.getSystemResource(privTransactionSigningKey).toURI())) .build(); diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyAcceptanceTest.java index e2fdd2188b1..b1c175f6461 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyAcceptanceTest.java @@ -104,6 +104,7 @@ public void setUp() throws Exception { "http://127.0.0.1:" + wireMockRule.port(), "authentication/auth_priv.toml", "authentication/auth_priv_key", + false, false); multiTenancyCluster.start(node); final String token = diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyPrivateNonceIncrementingTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyPrivateNonceIncrementingTest.java new file mode 100644 index 00000000000..11c405c37a8 --- /dev/null +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyPrivateNonceIncrementingTest.java @@ -0,0 +1,241 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.tests.acceptance.privacy.multitenancy; + +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static java.nio.charset.StandardCharsets.UTF_8; + +import org.hyperledger.besu.crypto.KeyPair; +import org.hyperledger.besu.crypto.SignatureAlgorithm; +import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.enclave.types.PrivacyGroup; +import org.hyperledger.besu.enclave.types.ReceiveResponse; +import org.hyperledger.besu.enclave.types.SendResponse; +import org.hyperledger.besu.ethereum.privacy.PrivateTransaction; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import org.hyperledger.besu.plugin.data.Restriction; +import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; +import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; +import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.Cluster; +import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.ClusterConfiguration; +import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.ClusterConfigurationBuilder; + +import java.math.BigInteger; +import java.util.List; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import org.apache.tuweni.bytes.Bytes; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +public class MultiTenancyPrivateNonceIncrementingTest extends AcceptanceTestBase { + private BesuNode node; + private final ObjectMapper mapper = new ObjectMapper(); + private Cluster multiTenancyCluster; + + private static final Supplier SIGNATURE_ALGORITHM = + Suppliers.memoize(SignatureAlgorithmFactory::getInstance); + private static final KeyPair TEST_KEY = + SIGNATURE_ALGORITHM + .get() + .createKeyPair( + SIGNATURE_ALGORITHM + .get() + .createPrivateKey( + new BigInteger( + "853d7f0010fd86d0d7811c1f9d968ea89a24484a8127b4a483ddf5d2cfec766d", 16))); + private static final String PRIVACY_GROUP_ID = "B1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; + private static final String PARTICIPANT_ENCLAVE_KEY0 = + "A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; + private static final String PARTICIPANT_ENCLAVE_KEY1 = + "sgFkVOyFndZe/5SAZJO5UYbrl7pezHetveriBBWWnE8="; + private final Address senderAddress = + Address.wrap(Bytes.fromHexString(accounts.getPrimaryBenefactor().getAddress())); + + @Rule public WireMockRule wireMockRule = new WireMockRule(options().dynamicPort()); + + @Before + public void setUp() throws Exception { + final ClusterConfiguration clusterConfiguration = + new ClusterConfigurationBuilder().awaitPeerDiscovery(false).build(); + multiTenancyCluster = new Cluster(clusterConfiguration, net); + node = + besu.createNodeWithMultiTenantedPrivacy( + "node1", + "http://127.0.0.1:" + wireMockRule.port(), + "authentication/auth_priv.toml", + "authentication/auth_priv_key", + false, + true); + multiTenancyCluster.start(node); + final String token = + node.execute(permissioningTransactions.createSuccessfulLogin("user", "pegasys")); + node.useAuthenticationTokenInHeaderForJsonRpc(token); + } + + @After + public void tearDown() { + multiTenancyCluster.close(); + } + + @Test + public void validateUnsuccessfulPrivateTransactionsNonceIncrementation() + throws JsonProcessingException { + executePrivateFailingTransaction(0, 0, 1); + executePrivateValidTransaction(1, 1, 2); + executePrivateFailingTransaction(2, 2, 3); + executePrivateFailingTransaction(3, 3, 4); + executePrivateValidTransaction(4, 4, 5); + } + + private void executePrivateValidTransaction( + final int nonce, + final int expectedTransactionCountBeforeExecution, + final int expectedTransactionCountAfterExecution) + throws JsonProcessingException { + final PrivateTransaction validSignedPrivateTransaction = + getValidSignedPrivateTransaction(senderAddress, nonce); + + final String accountAddress = validSignedPrivateTransaction.getSender().toHexString(); + final BytesValueRLPOutput rlpOutput = getRLPOutput(validSignedPrivateTransaction); + + processEnclaveStub(validSignedPrivateTransaction); + + node.verify( + priv.getTransactionCount( + accountAddress, PRIVACY_GROUP_ID, expectedTransactionCountBeforeExecution)); + + final Hash transactionReceipt = + node.execute(privacyTransactions.sendRawTransaction(rlpOutput.encoded().toHexString())); + + node.verify(priv.getSuccessfulTransactionReceipt(transactionReceipt)); + node.verify( + priv.getTransactionCount( + accountAddress, PRIVACY_GROUP_ID, expectedTransactionCountAfterExecution)); + } + + private void executePrivateFailingTransaction( + final int nonce, + final int expectedTransactionCountBeforeExecution, + final int expectedTransactionCountAfterExecution) + throws JsonProcessingException { + final PrivateTransaction invalidSignedPrivateTransaction = + getInvalidSignedPrivateTransaction(senderAddress, nonce); + final String accountAddress = invalidSignedPrivateTransaction.getSender().toHexString(); + final BytesValueRLPOutput invalidTxRlp = getRLPOutput(invalidSignedPrivateTransaction); + + processEnclaveStub(invalidSignedPrivateTransaction); + + node.verify( + priv.getTransactionCount( + accountAddress, PRIVACY_GROUP_ID, expectedTransactionCountBeforeExecution)); + final Hash invalidTransactionReceipt = + node.execute(privacyTransactions.sendRawTransaction(invalidTxRlp.encoded().toHexString())); + + node.verify(priv.getFailedTransactionReceipt(invalidTransactionReceipt)); + node.verify( + priv.getTransactionCount( + accountAddress, PRIVACY_GROUP_ID, expectedTransactionCountAfterExecution)); + } + + private void processEnclaveStub(final PrivateTransaction validSignedPrivateTransaction) + throws JsonProcessingException { + retrievePrivacyGroupEnclaveStub(); + sendEnclaveStub(); + receiveEnclaveStub(validSignedPrivateTransaction); + } + + private void retrievePrivacyGroupEnclaveStub() throws JsonProcessingException { + final String retrieveGroupResponse = + mapper.writeValueAsString( + createPrivacyGroup( + List.of(PARTICIPANT_ENCLAVE_KEY0, PARTICIPANT_ENCLAVE_KEY1), + PrivacyGroup.Type.PANTHEON)); + stubFor(post("/retrievePrivacyGroup").willReturn(ok(retrieveGroupResponse))); + } + + private void sendEnclaveStub() throws JsonProcessingException { + final String sendResponse = + mapper.writeValueAsString(new SendResponse(PARTICIPANT_ENCLAVE_KEY1)); + stubFor(post("/send").willReturn(ok(sendResponse))); + } + + private void receiveEnclaveStub(final PrivateTransaction privTx) throws JsonProcessingException { + final BytesValueRLPOutput rlpOutput = getRLPOutput(privTx); + final String senderKey = privTx.getPrivateFrom().toBase64String(); + final String receiveResponse = + mapper.writeValueAsString( + new ReceiveResponse( + rlpOutput.encoded().toBase64String().getBytes(UTF_8), PRIVACY_GROUP_ID, senderKey)); + stubFor(post("/receive").willReturn(ok(receiveResponse))); + } + + private BytesValueRLPOutput getRLPOutput(final PrivateTransaction privateTransaction) { + final BytesValueRLPOutput bvrlpo = new BytesValueRLPOutput(); + privateTransaction.writeTo(bvrlpo); + return bvrlpo; + } + + private PrivacyGroup createPrivacyGroup( + final List groupMembers, final PrivacyGroup.Type groupType) { + return new PrivacyGroup(PRIVACY_GROUP_ID, groupType, "test", "testGroup", groupMembers); + } + + private static PrivateTransaction getInvalidSignedPrivateTransaction( + final Address senderAddress, final int nonce) { + return PrivateTransaction.builder() + .nonce(nonce) + .gasPrice(Wei.ZERO) + .gasLimit(3000000) + .to(null) + .value(Wei.ZERO) + .payload(Bytes.fromHexString("0x1234")) + .sender(senderAddress) + .chainId(BigInteger.valueOf(1337)) + .privateFrom(Bytes.fromBase64String(PARTICIPANT_ENCLAVE_KEY0)) + .restriction(Restriction.RESTRICTED) + .privacyGroupId(Bytes.fromBase64String(PRIVACY_GROUP_ID)) + .signAndBuild(TEST_KEY); + } + + private static PrivateTransaction getValidSignedPrivateTransaction( + final Address senderAddress, final int nonce) { + return PrivateTransaction.builder() + .nonce(nonce) + .gasPrice(Wei.ZERO) + .gasLimit(3000000) + .to(null) + .value(Wei.ZERO) + .payload(Bytes.wrap(new byte[] {})) + .sender(senderAddress) + .chainId(BigInteger.valueOf(1337)) + .privateFrom(Bytes.fromBase64String(PARTICIPANT_ENCLAVE_KEY0)) + .restriction(Restriction.RESTRICTED) + .privacyGroupId(Bytes.fromBase64String(PRIVACY_GROUP_ID)) + .signAndBuild(TEST_KEY); + } +} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyValidationFailAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyValidationFailAcceptanceTest.java index 84e6fa7ea3e..28bf140bb36 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyValidationFailAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyValidationFailAcceptanceTest.java @@ -78,6 +78,7 @@ public void setUp() throws Exception { "http://127.0.0.1:" + wireMockRule.port(), "authentication/auth_priv.toml", "authentication/auth_priv_key", + false, false); multiTenancyCluster.start(node); 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 12752f2e9a1..8b123e70d36 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -715,6 +715,13 @@ static class PrivacyOptionGroup { names = {"--privacy-flexible-groups-enabled"}, description = "Enable flexible privacy groups (default: ${DEFAULT-VALUE})") private final Boolean isFlexiblePrivacyGroupsEnabled = false; + + @Option( + names = {"--privacy-nonce-always-increments"}, + description = + "Enable private nonce " + + "incrementation even if the transaction didn't succeeded (default: ${DEFAULT-VALUE})") + private final Boolean isPrivateNonceAlwaysIncrementsEnabled = false; } // Metrics Option Group @@ -2062,6 +2069,8 @@ private PrivacyParameters privacyParameters() { privacyOptionGroup.isFlexiblePrivacyGroupsEnabled); privacyParametersBuilder.setPrivacyPluginEnabled( unstablePrivacyPluginOptions.isPrivacyPluginEnabled()); + privacyParametersBuilder.setPrivateNonceAlwaysIncrementsEnabled( + privacyOptionGroup.isPrivateNonceAlwaysIncrementsEnabled); final boolean hasPrivacyPublicKey = privacyOptionGroup.privacyPublicKeyFile != null; diff --git a/besu/src/test/resources/everything_config.toml b/besu/src/test/resources/everything_config.toml index d5cc291237a..a5e03bd5990 100644 --- a/besu/src/test/resources/everything_config.toml +++ b/besu/src/test/resources/everything_config.toml @@ -172,6 +172,7 @@ privacy-multi-tenancy-enabled=true privacy-marker-transaction-signing-key-file="./signerKey" privacy-enable-database-migration=false privacy-flexible-groups-enabled=false +privacy-nonce-always-increments=false # Transaction Pool tx-pool="layered" diff --git a/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractIntegrationTest.java b/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractIntegrationTest.java index 090c24e26e2..a5ee5f068ee 100644 --- a/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractIntegrationTest.java +++ b/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractIntegrationTest.java @@ -205,6 +205,7 @@ public void testSendAndReceive() { new PrivateStateRootResolver(privateStateStorage), new PrivateStateGenesisAllocator( false, (privacyGroupId, blockNumber) -> Collections::emptyList), + false, "IntegrationTest"); privacyPrecompiledContract.setPrivateTransactionProcessor(mockPrivateTxProcessor()); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/PrivacyParameters.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/PrivacyParameters.java index 349ff8ffbee..775634a41a1 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/PrivacyParameters.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/PrivacyParameters.java @@ -77,6 +77,7 @@ public class PrivacyParameters { private PrivateStateRootResolver privateStateRootResolver; private PrivateWorldStateReader privateWorldStateReader; private PrivacyPluginService privacyPluginService; + private boolean privateNonceAlwaysIncrementsEnabled; public Address getPrivacyAddress() { if (isPrivacyPluginEnabled()) { @@ -228,6 +229,15 @@ private PrivacyGroupGenesisProvider createPrivateGenesisProvider() { } } + public boolean isPrivateNonceAlwaysIncrementsEnabled() { + return privateNonceAlwaysIncrementsEnabled; + } + + public void setPrivateNonceAlwaysIncrementsEnabled( + final boolean privateNonceAlwaysIncrementsEnabled) { + this.privateNonceAlwaysIncrementsEnabled = privateNonceAlwaysIncrementsEnabled; + } + @Override public String toString() { return "PrivacyParameters{" @@ -263,6 +273,7 @@ public static class Builder { private boolean flexiblePrivacyGroupsEnabled; private boolean privacyPluginEnabled; private PrivacyPluginService privacyPluginService; + private boolean privateNonceAlwaysIncrementsEnabled; public Builder setEnclaveUrl(final URI enclaveUrl) { this.enclaveUrl = enclaveUrl; @@ -314,6 +325,12 @@ public Builder setFlexiblePrivacyGroupsEnabled(final boolean flexiblePrivacyGrou return this; } + public Builder setPrivateNonceAlwaysIncrementsEnabled( + final boolean isPrivateNonceAlwaysIncrementsEnabled) { + this.privateNonceAlwaysIncrementsEnabled = isPrivateNonceAlwaysIncrementsEnabled; + return this; + } + public Builder setPrivacyPluginEnabled(final boolean privacyPluginEnabled) { this.privacyPluginEnabled = privacyPluginEnabled; return this; @@ -382,6 +399,7 @@ public PrivacyParameters build() { config.setMultiTenancyEnabled(multiTenancyEnabled); config.setFlexiblePrivacyGroupsEnabled(flexiblePrivacyGroupsEnabled); config.setPrivacyPluginEnabled(privacyPluginEnabled); + config.setPrivateNonceAlwaysIncrementsEnabled(privateNonceAlwaysIncrementsEnabled); return config; } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/FlexiblePrivacyPrecompiledContract.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/FlexiblePrivacyPrecompiledContract.java index 0dd6863f94e..98709a4eaad 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/FlexiblePrivacyPrecompiledContract.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/FlexiblePrivacyPrecompiledContract.java @@ -70,13 +70,15 @@ public FlexiblePrivacyPrecompiledContract( final Enclave enclave, final WorldStateArchive worldStateArchive, final PrivateStateRootResolver privateStateRootResolver, - final PrivateStateGenesisAllocator privateStateGenesisAllocator) { + final PrivateStateGenesisAllocator privateStateGenesisAllocator, + final boolean alwaysIncrementPrivateNonce) { super( gasCalculator, enclave, worldStateArchive, privateStateRootResolver, privateStateGenesisAllocator, + alwaysIncrementPrivateNonce, "FlexiblePrivacy"); } @@ -87,7 +89,8 @@ public FlexiblePrivacyPrecompiledContract( privacyParameters.getEnclave(), privacyParameters.getPrivateWorldStateArchive(), privacyParameters.getPrivateStateRootResolver(), - privacyParameters.getPrivateStateGenesisAllocator()); + privacyParameters.getPrivateStateGenesisAllocator(), + privacyParameters.isPrivateNonceAlwaysIncrementsEnabled()); } public long addPrivateTransactionObserver(final PrivateTransactionObserver observer) { 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 b1a754c9fe2..7e9bfa1ac4b 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 @@ -19,6 +19,7 @@ import static org.hyperledger.besu.ethereum.mainnet.PrivateStateUtils.KEY_TRANSACTION_HASH; import static org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver.EMPTY_ROOT_HASH; +import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.enclave.Enclave; import org.hyperledger.besu.enclave.EnclaveClientException; @@ -40,6 +41,7 @@ import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.evm.account.MutableAccount; import org.hyperledger.besu.evm.frame.BlockValues; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -61,6 +63,7 @@ public class PrivacyPrecompiledContract extends AbstractPrecompiledContract { final WorldStateArchive privateWorldStateArchive; final PrivateStateRootResolver privateStateRootResolver; private final PrivateStateGenesisAllocator privateStateGenesisAllocator; + final boolean alwaysIncrementPrivateNonce; PrivateTransactionProcessor privateTransactionProcessor; private static final Logger LOG = LoggerFactory.getLogger(PrivacyPrecompiledContract.class); @@ -79,6 +82,7 @@ public PrivacyPrecompiledContract( privacyParameters.getPrivateWorldStateArchive(), privacyParameters.getPrivateStateRootResolver(), privacyParameters.getPrivateStateGenesisAllocator(), + privacyParameters.isPrivateNonceAlwaysIncrementsEnabled(), name); } @@ -88,12 +92,14 @@ protected PrivacyPrecompiledContract( final WorldStateArchive worldStateArchive, final PrivateStateRootResolver privateStateRootResolver, final PrivateStateGenesisAllocator privateStateGenesisAllocator, + final boolean alwaysIncrementPrivateNonce, final String name) { super(name, gasCalculator); this.enclave = enclave; this.privateWorldStateArchive = worldStateArchive; this.privateStateRootResolver = privateStateRootResolver; this.privateStateGenesisAllocator = privateStateGenesisAllocator; + this.alwaysIncrementPrivateNonce = alwaysIncrementPrivateNonce; } public void setPrivateTransactionProcessor( @@ -181,18 +187,31 @@ public PrecompileContractResult computePrecompile( processPrivateTransaction( messageFrame, privateTransaction, privacyGroupId, privateWorldStateUpdater); - if (result.isInvalid() || !result.isSuccessful()) { + final Boolean isPersistingPrivateState = + messageFrame.getContextVariable(KEY_IS_PERSISTING_PRIVATE_STATE, false); + + if (!result.isSuccessful()) { LOG.error( "Failed to process private transaction {}: {}", pmtHash, result.getValidationResult().getErrorMessage()); - - privateMetadataUpdater.putTransactionReceipt(pmtHash, new PrivateTransactionReceipt(result)); - + if (isPersistingPrivateState && alwaysIncrementPrivateNonce) { + final Address senderAddress = privateTransaction.getSender(); + final MutableAccount senderAccount = privateWorldStateUpdater.getOrCreate(senderAddress); + senderAccount.incrementNonce(); + // we can safely commit the updater here, because it is only changed if the transaction is + // successful, + // so we can be sure that the only change is the incremented nonce + privateWorldStateUpdater.commit(); + disposablePrivateState.persist(null); + + storePrivateMetadata( + pmtHash, privacyGroupId, disposablePrivateState, privateMetadataUpdater, result); + } return NO_RESULT; } - if (messageFrame.getContextVariable(KEY_IS_PERSISTING_PRIVATE_STATE, false)) { + if (isPersistingPrivateState) { privateWorldStateUpdater.commit(); disposablePrivateState.persist(null); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/FlexiblePrivacyPrecompiledContractTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/FlexiblePrivacyPrecompiledContractTest.java index 3c4711a194a..fc08f519d3d 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/FlexiblePrivacyPrecompiledContractTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/FlexiblePrivacyPrecompiledContractTest.java @@ -383,7 +383,8 @@ public void testInvalidPrivateTransaction() { enclave, worldStateArchive, privateStateRootResolver, - privateStateGenesisAllocator); + privateStateGenesisAllocator, + false); contract.setPrivateTransactionProcessor( mockPrivateTxProcessor( @@ -427,6 +428,7 @@ private FlexiblePrivacyPrecompiledContract buildPrivacyPrecompiledContract( enclave, worldStateArchive, privateStateRootResolver, - privateStateGenesisAllocator); + privateStateGenesisAllocator, + false); } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java index 134cfa9eac6..7d253057ac7 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java @@ -302,6 +302,7 @@ public void testInvalidPrivateTransaction() { worldStateArchive, privateStateRootResolver, privateStateGenesisAllocator, + false, "RestrictedPrivacyTest"); contract.setPrivateTransactionProcessor( @@ -328,7 +329,7 @@ public void testInvalidPrivateTransaction() { @Test public void testSimulatedPublicTransactionIsSkipped() { final PrivacyPrecompiledContract emptyContract = - new PrivacyPrecompiledContract(null, null, null, null, null, null); + new PrivacyPrecompiledContract(null, null, null, null, null, false, null); // A simulated public transaction doesn't contain a PrivateMetadataUpdater final MessageFrame frame = mock(MessageFrame.class); @@ -355,6 +356,7 @@ private PrivacyPrecompiledContract buildPrivacyPrecompiledContract(final Enclave worldStateArchive, privateStateRootResolver, privateStateGenesisAllocator, + false, "PrivacyTests"); } }