Skip to content

Commit

Permalink
Increment private nonce even if transaction failed. (hyperledger#6593)
Browse files Browse the repository at this point in the history
Increment private nonce even if transaction failed

Signed-off-by: George Tebrean <george@web3labs.com>
Signed-off-by: stefan.pingel@consensys.net <stefan.pingel@consensys.net>
Co-authored-by: stefan.pingel@consensys.net <stefan.pingel@consensys.net>
Co-authored-by: Stefan Pingel <16143240+pinges@users.noreply.github.com>
  • Loading branch information
3 people authored and daniellehrner committed Jul 16, 2024
1 parent fa9fb5f commit 258d9fb
Show file tree
Hide file tree
Showing 14 changed files with 315 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
- Added EIP-7702 [#7237](https://github.com/hyperledger/besu/pull/7237)

### Bug fixes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
Original file line number Diff line number Diff line change
@@ -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<SignatureAlgorithm> 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<String> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
9 changes: 9 additions & 0 deletions besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -2062,6 +2069,8 @@ private PrivacyParameters privacyParameters() {
privacyOptionGroup.isFlexiblePrivacyGroupsEnabled);
privacyParametersBuilder.setPrivacyPluginEnabled(
unstablePrivacyPluginOptions.isPrivacyPluginEnabled());
privacyParametersBuilder.setPrivateNonceAlwaysIncrementsEnabled(
privacyOptionGroup.isPrivateNonceAlwaysIncrementsEnabled);

final boolean hasPrivacyPublicKey = privacyOptionGroup.privacyPublicKeyFile != null;

Expand Down
1 change: 1 addition & 0 deletions besu/src/test/resources/everything_config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ public void testSendAndReceive() {
new PrivateStateRootResolver(privateStateStorage),
new PrivateStateGenesisAllocator(
false, (privacyGroupId, blockNumber) -> Collections::emptyList),
false,
"IntegrationTest");

privacyPrecompiledContract.setPrivateTransactionProcessor(mockPrivateTxProcessor());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public class PrivacyParameters {
private PrivateStateRootResolver privateStateRootResolver;
private PrivateWorldStateReader privateWorldStateReader;
private PrivacyPluginService privacyPluginService;
private boolean privateNonceAlwaysIncrementsEnabled;

public Address getPrivacyAddress() {
if (isPrivacyPluginEnabled()) {
Expand Down Expand Up @@ -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{"
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -382,6 +399,7 @@ public PrivacyParameters build() {
config.setMultiTenancyEnabled(multiTenancyEnabled);
config.setFlexiblePrivacyGroupsEnabled(flexiblePrivacyGroupsEnabled);
config.setPrivacyPluginEnabled(privacyPluginEnabled);
config.setPrivateNonceAlwaysIncrementsEnabled(privateNonceAlwaysIncrementsEnabled);
return config;
}
}
Expand Down
Loading

0 comments on commit 258d9fb

Please sign in to comment.