[Issue 1975] Add option to require tx replay protection (#2497)

* Add option to enforce tx replay protection for local txs
* Only enforce replay protection if the current milestone supports it
* moved changelog entry to next release

Signed-off-by: Meredith Baxter <meredith.baxter@palm.io>
Signed-off-by: Sally MacFarlane <sally.macfarlane@consensys.net>
Co-authored-by: Sally MacFarlane <sally.macfarlane@consensys.net>
sonarTour
mbaxter 3 years ago committed by GitHub
parent 19ca28f9a2
commit e860de96ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 7
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/eth/EthConditions.java
  3. 43
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/eth/ExpectSuccessfulEthSendRawTransaction.java
  4. 24
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/BesuNode.java
  5. 5
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/Node.java
  6. 3
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java
  7. 13
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java
  8. 9
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfiguration.java
  9. 10
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfigurationBuilder.java
  10. 96
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java
  11. 31
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/genesis/GenesisConfigurationFactory.java
  12. 7
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/privacy/PrivacyNodeFactory.java
  13. 3
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/PrivacyNode.java
  14. 67
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/SignUtil.java
  15. 23
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/TransactionWithSignatureAlgorithm.java
  16. 49
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/account/AccountTransactions.java
  17. 62
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/account/TransferTransaction.java
  18. 27
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/account/TransferTransactionBuilder.java
  19. 6
      acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/backup/BackupRoundTripAcceptanceTest.java
  20. 2
      acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/crypto/SECP256R1AcceptanceTest.java
  21. 120
      acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/EthSendRawTransactionTest.java
  22. 2
      acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AccountLocalAndOnchainPermissioningAcceptanceTest.java
  23. 2
      acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AccountLocalConfigPermissioningAcceptanceTest.java
  24. 2
      acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AccountSmartContractPermissioningAcceptanceTest.java
  25. 43
      acceptance-tests/tests/src/test/resources/dev/dev_london.json
  26. 15
      besu/src/main/java/org/hyperledger/besu/cli/options/unstable/TransactionPoolOptions.java
  27. 50
      besu/src/test/java/org/hyperledger/besu/cli/options/TransactionPoolOptionsTest.java
  28. 1
      besu/src/test/resources/everything_config.toml
  29. 2
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcErrorConverter.java
  30. 1
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcError.java
  31. 4
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java
  32. 4
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpec.java
  33. 1
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java
  34. 33
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPool.java
  35. 6
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolConfiguration.java
  36. 3
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactoryTest.java
  37. 180
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolTest.java

@ -6,6 +6,7 @@
### Additions and Improvements
- Genesis file parameter `blockperiodseconds` is validated as a positive integer on startup to prevent unexpected runtime behaviour [#3186](https://github.com/hyperledger/besu/pull/3186)
- Add option to require replay protection for locally submitted transactions [\#1975](https://github.com/hyperledger/besu/issues/1975)
### Bug Fixes

@ -51,12 +51,17 @@ public class EthConditions {
transactions.getTransactionReceipt(transactionHash));
}
public Condition sendRawTransactionExceptional(
public Condition expectEthSendRawTransactionException(
final String transactionData, final String expectedMessage) {
return new ExpectEthSendRawTransactionException(
transactions.sendRawTransaction(transactionData), expectedMessage);
}
public Condition expectSuccessfulEthRawTransaction(final String transactionData) {
return new ExpectSuccessfulEthSendRawTransaction(
transactions.sendRawTransaction(transactionData));
}
public Condition expectSuccessfulTransactionReceiptWithReason(
final String transactionHash, final String revertReason) {
return new ExpectSuccessfulEthGetTransactionReceiptWithReason(

@ -0,0 +1,43 @@
/*
* 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.tests.acceptance.dsl.condition.eth;
import static org.assertj.core.api.Assertions.assertThat;
import org.hyperledger.besu.tests.acceptance.dsl.WaitUtils;
import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition;
import org.hyperledger.besu.tests.acceptance.dsl.node.Node;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.eth.EthSendRawTransactionTransaction;
import org.apache.tuweni.bytes.Bytes32;
public class ExpectSuccessfulEthSendRawTransaction implements Condition {
private final EthSendRawTransactionTransaction transaction;
public ExpectSuccessfulEthSendRawTransaction(final EthSendRawTransactionTransaction transaction) {
this.transaction = transaction;
}
@Override
public void verify(final Node node) {
WaitUtils.waitFor(
5,
() -> {
final Bytes32 txHash = Bytes32.fromHexString(node.execute(transaction));
assertThat(txHash).isNotNull();
});
}
}

@ -21,7 +21,6 @@ import static org.apache.tuweni.io.file.Files.copyResource;
import org.hyperledger.besu.cli.config.NetworkName;
import org.hyperledger.besu.crypto.KeyPair;
import org.hyperledger.besu.crypto.KeyPairUtil;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration;
@ -38,7 +37,6 @@ import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.NodeConfigur
import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.genesis.GenesisConfigurationProvider;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.TransactionWithSignatureAlgorithm;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.admin.AdminRequestFactory;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.bft.BftRequestFactory;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.bft.ConsensusType;
@ -121,6 +119,7 @@ public class BesuNode implements NodeConfiguration, RunnableNode, AutoCloseable
private boolean isDnsEnabled = false;
private Optional<Integer> exitCode = Optional.empty();
private Optional<PkiKeyStoreConfiguration> pkiKeyStoreConfiguration = Optional.empty();
private final boolean isStrictTxReplayProtectionEnabled;
public BesuNode(
final String name,
@ -150,9 +149,11 @@ public class BesuNode implements NodeConfiguration, RunnableNode, AutoCloseable
final Optional<PrivacyParameters> privacyParameters,
final List<String> runCommand,
final Optional<KeyPair> keyPair,
final Optional<PkiKeyStoreConfiguration> pkiKeyStoreConfiguration)
final Optional<PkiKeyStoreConfiguration> pkiKeyStoreConfiguration,
final boolean isStrictTxReplayProtectionEnabled)
throws IOException {
this.homeDirectory = dataPath.orElseGet(BesuNode::createTmpDataDirectory);
this.isStrictTxReplayProtectionEnabled = isStrictTxReplayProtectionEnabled;
keyfilePath.ifPresent(
path -> {
try {
@ -359,10 +360,8 @@ public class BesuNode implements NodeConfiguration, RunnableNode, AutoCloseable
websocketService = Optional.of((WebSocketService) web3jService);
} else {
web3jService =
jsonRpcBaseUrl()
.map(HttpService::new)
.orElse(new HttpService("http://" + LOCALHOST + ":" + 8545));
final String url = jsonRpcBaseUrl().orElse("http://" + LOCALHOST + ":" + 8545);
web3jService = new HttpService(url);
if (token != null) {
((HttpService) web3jService).addHeader("Authorization", "Bearer " + token);
}
@ -672,6 +671,10 @@ public class BesuNode implements NodeConfiguration, RunnableNode, AutoCloseable
return pkiKeyStoreConfiguration;
}
public boolean isStrictTxReplayProtectionEnabled() {
return isStrictTxReplayProtectionEnabled;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
@ -723,13 +726,6 @@ public class BesuNode implements NodeConfiguration, RunnableNode, AutoCloseable
return transaction.execute(nodeRequests());
}
@Override
public <T> T execute(
final TransactionWithSignatureAlgorithm<T> transaction,
final SignatureAlgorithm signatureAlgorithm) {
return transaction.execute(nodeRequests(), signatureAlgorithm);
}
@Override
public void verify(final Condition expected) {
expected.verify(this);

@ -14,17 +14,12 @@
*/
package org.hyperledger.besu.tests.acceptance.dsl.node;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.TransactionWithSignatureAlgorithm;
public interface Node {
<T> T execute(Transaction<T> transaction);
<T> T execute(
TransactionWithSignatureAlgorithm<T> transaction, SignatureAlgorithm signatureAlgorithm);
void verify(final Condition expected);
}

@ -362,6 +362,9 @@ public class ProcessBesuNodeRunner implements BesuNodeRunner {
params.add("--auto-log-bloom-caching-enabled");
params.add("false");
params.add("--strict-tx-replay-protection-enabled");
params.add(Boolean.toString(node.isStrictTxReplayProtectionEnabled()));
final String level = System.getProperty("root.log.level");
if (level != null) {
params.add("--logging=" + level);

@ -14,12 +14,12 @@
*/
package org.hyperledger.besu.tests.acceptance.dsl.node;
import static org.hyperledger.besu.cli.config.NetworkName.DEV;
import static org.hyperledger.besu.controller.BesuController.DATABASE_PATH;
import org.hyperledger.besu.Runner;
import org.hyperledger.besu.RunnerBuilder;
import org.hyperledger.besu.cli.config.EthNetworkConfig;
import org.hyperledger.besu.cli.config.NetworkName;
import org.hyperledger.besu.consensus.qbft.pki.PkiBlockCreationConfigurationProvider;
import org.hyperledger.besu.controller.BesuController;
import org.hyperledger.besu.controller.BesuControllerBuilder;
@ -30,6 +30,7 @@ import org.hyperledger.besu.ethereum.GasLimitCalculator;
import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration;
import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration;
import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration;
import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration;
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl;
import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStorageProvider;
@ -137,8 +138,9 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner {
node.getConfiguration().getBootnodes().stream()
.map(EnodeURLImpl::fromURI)
.collect(Collectors.toList());
final NetworkName network = node.getNetwork() == null ? NetworkName.DEV : node.getNetwork();
final EthNetworkConfig.Builder networkConfigBuilder =
new EthNetworkConfig.Builder(EthNetworkConfig.getNetworkConfig(DEV))
new EthNetworkConfig.Builder(EthNetworkConfig.getNetworkConfig(network))
.setBootNodes(bootnodes);
node.getConfiguration().getGenesisConfig().ifPresent(networkConfigBuilder::setGenesisConfig);
final EthNetworkConfig ethNetworkConfig = networkConfigBuilder.build();
@ -152,6 +154,11 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner {
.withMetricsSystem(metricsSystem)
.build();
final TransactionPoolConfiguration txPoolConfig =
ImmutableTransactionPoolConfiguration.builder()
.strictTransactionReplayProtectionEnabled(node.isStrictTxReplayProtectionEnabled())
.build();
final BesuController besuController =
builder
.synchronizerConfiguration(new SynchronizerConfiguration.Builder().build())
@ -160,7 +167,7 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner {
.privacyParameters(node.getPrivacyParameters())
.nodeKey(new NodeKey(new KeyPairSecurityModule(KeyPairUtil.loadKeyPair(dataDir))))
.metricsSystem(metricsSystem)
.transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT)
.transactionPoolConfiguration(txPoolConfig)
.ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig())
.clock(Clock.systemUTC())
.isRevertReasonEnabled(node.isRevertReasonEnabled())

@ -61,6 +61,7 @@ public class BesuNodeConfiguration {
private final NetworkName network;
private final Optional<KeyPair> keyPair;
private final Optional<PkiKeyStoreConfiguration> pkiKeyStoreConfiguration;
private final boolean strictTxReplayProtectionEnabled;
BesuNodeConfiguration(
final String name,
@ -90,7 +91,8 @@ public class BesuNodeConfiguration {
final Optional<PrivacyParameters> privacyParameters,
final List<String> runCommand,
final Optional<KeyPair> keyPair,
final Optional<PkiKeyStoreConfiguration> pkiKeyStoreConfiguration) {
final Optional<PkiKeyStoreConfiguration> pkiKeyStoreConfiguration,
final boolean strictTxReplayProtectionEnabled) {
this.name = name;
this.miningParameters = miningParameters;
this.jsonRpcConfiguration = jsonRpcConfiguration;
@ -119,6 +121,7 @@ public class BesuNodeConfiguration {
this.runCommand = runCommand;
this.keyPair = keyPair;
this.pkiKeyStoreConfiguration = pkiKeyStoreConfiguration;
this.strictTxReplayProtectionEnabled = strictTxReplayProtectionEnabled;
}
public String getName() {
@ -232,4 +235,8 @@ public class BesuNodeConfiguration {
public Optional<PkiKeyStoreConfiguration> getPkiKeyStoreConfiguration() {
return pkiKeyStoreConfiguration;
}
public boolean isStrictTxReplayProtectionEnabled() {
return strictTxReplayProtectionEnabled;
}
}

@ -81,6 +81,7 @@ public class BesuNodeConfigurationBuilder {
private List<String> runCommand = new ArrayList<>();
private Optional<KeyPair> keyPair = Optional.empty();
private Optional<PkiKeyStoreConfiguration> pkiKeyStoreConfiguration = Optional.empty();
private Boolean strictTxReplayProtectionEnabled = false;
public BesuNodeConfigurationBuilder() {
// Check connections more frequently during acceptance tests to cut down on
@ -418,6 +419,12 @@ public class BesuNodeConfigurationBuilder {
return this;
}
public BesuNodeConfigurationBuilder strictTxReplayProtectionEnabled(
final Boolean strictTxReplayProtectionEnabled) {
this.strictTxReplayProtectionEnabled = strictTxReplayProtectionEnabled;
return this;
}
public BesuNodeConfiguration build() {
return new BesuNodeConfiguration(
name,
@ -447,6 +454,7 @@ public class BesuNodeConfigurationBuilder {
privacyParameters,
runCommand,
keyPair,
pkiKeyStoreConfiguration);
pkiKeyStoreConfiguration,
strictTxReplayProtectionEnabled);
}
}

@ -46,13 +46,12 @@ import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import io.vertx.core.Vertx;
public class BesuNodeFactory {
private final GenesisConfigurationFactory genesis = new GenesisConfigurationFactory();
private final NodeConfigurationFactory node = new NodeConfigurationFactory();
private final PkiKeystoreConfigurationFactory pkiKeystoreConfigurationFactory =
new PkiKeystoreConfigurationFactory();
@ -86,43 +85,54 @@ public class BesuNodeFactory {
config.getPrivacyParameters(),
config.getRunCommand(),
config.getKeyPair(),
config.getPkiKeyStoreConfiguration());
config.getPkiKeyStoreConfiguration(),
config.isStrictTxReplayProtectionEnabled());
}
public BesuNode createMinerNode(final String name) throws IOException {
return create(
public BesuNode createMinerNode(
final String name, final UnaryOperator<BesuNodeConfigurationBuilder> configModifier)
throws IOException {
BesuNodeConfigurationBuilder builder =
new BesuNodeConfigurationBuilder()
.name(name)
.miningEnabled()
.jsonRpcEnabled()
.webSocketEnabled()
.build());
.webSocketEnabled();
builder = configModifier.apply(builder);
final BesuNodeConfiguration config = builder.build();
return create(config);
}
public BesuNode createMinerNode(final String name) throws IOException {
return createMinerNode(name, UnaryOperator.identity());
}
public BesuNode createMinerNodeWithRevertReasonEnabled(final String name) throws IOException {
return create(
new BesuNodeConfigurationBuilder()
.name(name)
.miningEnabled()
.jsonRpcEnabled()
.webSocketEnabled()
.revertReasonEnabled()
.build());
return createMinerNode(name, BesuNodeConfigurationBuilder::revertReasonEnabled);
}
public BesuNode createArchiveNode(final String name) throws IOException {
return create(
return createArchiveNode(name, UnaryOperator.identity());
}
public BesuNode createArchiveNode(
final String name, final UnaryOperator<BesuNodeConfigurationBuilder> configModifier)
throws IOException {
BesuNodeConfigurationBuilder builder =
new BesuNodeConfigurationBuilder()
.name(name)
.jsonRpcEnabled()
.jsonRpcTxPool()
.webSocketEnabled()
.build());
.webSocketEnabled();
builder = configModifier.apply(builder);
return create(builder.build());
}
public BesuNode createNode(
final String name,
final Function<BesuNodeConfigurationBuilder, BesuNodeConfigurationBuilder> configModifier)
final String name, final UnaryOperator<BesuNodeConfigurationBuilder> configModifier)
throws IOException {
final BesuNodeConfigurationBuilder configBuilder =
configModifier.apply(new BesuNodeConfigurationBuilder().name(name));
@ -301,7 +311,7 @@ public class BesuNodeFactory {
.jsonRpcConfiguration(node.createJsonRpcWithCliqueEnabledConfig())
.webSocketConfiguration(node.createWebSocketEnabledConfig())
.devMode(false)
.genesisConfigProvider(genesis::createCliqueGenesisConfig)
.genesisConfigProvider(GenesisConfigurationFactory::createCliqueGenesisConfig)
.build());
}
@ -315,7 +325,8 @@ public class BesuNodeFactory {
.devMode(false)
.genesisConfigProvider(
validators ->
genesis.createIbft2GenesisConfigFilterBootnode(validators, genesisFile))
GenesisConfigurationFactory.createIbft2GenesisConfigFilterBootnode(
validators, genesisFile))
.bootnodeEligible(true)
.build());
}
@ -341,7 +352,8 @@ public class BesuNodeFactory {
.devMode(false)
.genesisConfigProvider(
validators ->
genesis.createIbft2GenesisConfigFilterBootnode(validators, genesisFile))
GenesisConfigurationFactory.createIbft2GenesisConfigFilterBootnode(
validators, genesisFile))
.bootnodeEligible(false)
.build());
}
@ -356,7 +368,8 @@ public class BesuNodeFactory {
.devMode(false)
.genesisConfigProvider(
validators ->
genesis.createIbft2GenesisConfigFilterBootnode(validators, genesisFile))
GenesisConfigurationFactory.createIbft2GenesisConfigFilterBootnode(
validators, genesisFile))
.bootnodeEligible(false)
.build());
}
@ -369,7 +382,7 @@ public class BesuNodeFactory {
.jsonRpcConfiguration(node.createJsonRpcWithIbft2EnabledConfig(false))
.webSocketConfiguration(node.createWebSocketEnabledConfig())
.devMode(false)
.genesisConfigProvider(genesis::createIbft2GenesisConfig)
.genesisConfigProvider(GenesisConfigurationFactory::createIbft2GenesisConfig)
.build());
}
@ -382,7 +395,7 @@ public class BesuNodeFactory {
.jsonRpcConfiguration(node.createJsonRpcWithQbftEnabledConfig(false))
.webSocketConfiguration(node.createWebSocketEnabledConfig())
.devMode(false)
.genesisConfigProvider(genesis::createQbftGenesisConfig)
.genesisConfigProvider(GenesisConfigurationFactory::createQbftGenesisConfig)
.build());
}
@ -406,7 +419,7 @@ public class BesuNodeFactory {
.jsonRpcConfiguration(node.createJsonRpcWithQbftEnabledConfig(false))
.webSocketConfiguration(node.createWebSocketEnabledConfig())
.devMode(false)
.genesisConfigProvider(genesis::createQbftGenesisConfig)
.genesisConfigProvider(GenesisConfigurationFactory::createQbftGenesisConfig)
.build());
}
@ -430,7 +443,7 @@ public class BesuNodeFactory {
.jsonRpcConfiguration(node.createJsonRpcWithQbftEnabledConfig(false))
.webSocketConfiguration(node.createWebSocketEnabledConfig())
.devMode(false)
.genesisConfigProvider(genesis::createQbftGenesisConfig)
.genesisConfigProvider(GenesisConfigurationFactory::createQbftGenesisConfig)
.pkiBlockCreationEnabled(pkiKeystoreConfigurationFactory.createPkiConfig(type, name))
.build());
}
@ -446,7 +459,7 @@ public class BesuNodeFactory {
final boolean canBeBootnode,
final boolean mining)
throws IOException {
final String genesisFile = genesis.readGenesisFile(genesisPath);
final String genesisFile = GenesisConfigurationFactory.readGenesisFile(genesisPath);
final BesuNodeConfigurationBuilder builder =
new BesuNodeConfigurationBuilder()
.name(name)
@ -476,7 +489,9 @@ public class BesuNodeFactory {
.genesisConfigProvider(
nodes ->
node.createGenesisConfigForValidators(
asList(validators), nodes, genesis::createCliqueGenesisConfig))
asList(validators),
nodes,
GenesisConfigurationFactory::createCliqueGenesisConfig))
.build());
}
@ -493,7 +508,9 @@ public class BesuNodeFactory {
.genesisConfigProvider(
nodes ->
node.createGenesisConfigForValidators(
asList(validators), nodes, genesis::createIbft2GenesisConfig))
asList(validators),
nodes,
GenesisConfigurationFactory::createIbft2GenesisConfig))
.build());
}
@ -511,7 +528,9 @@ public class BesuNodeFactory {
.genesisConfigProvider(
nodes ->
node.createGenesisConfigForValidators(
asList(validators), nodes, genesis::createIbft2GenesisConfig))
asList(validators),
nodes,
GenesisConfigurationFactory::createIbft2GenesisConfig))
.build());
}
@ -543,7 +562,9 @@ public class BesuNodeFactory {
.genesisConfigProvider(
nodes ->
node.createGenesisConfigForValidators(
asList(validators), nodes, genesis::createQbftGenesisConfig))
asList(validators),
nodes,
GenesisConfigurationFactory::createQbftGenesisConfig))
.build());
}
@ -561,7 +582,7 @@ public class BesuNodeFactory {
node.createGenesisConfigForValidators(
asList(validators),
nodes,
genesis::createQbftValidatorContractGenesisConfig))
GenesisConfigurationFactory::createQbftValidatorContractGenesisConfig))
.build());
}
@ -594,7 +615,9 @@ public class BesuNodeFactory {
.genesisConfigProvider(
nodes ->
node.createGenesisConfigForValidators(
asList(validators), nodes, genesis::createQbftGenesisConfig))
asList(validators),
nodes,
GenesisConfigurationFactory::createQbftGenesisConfig))
.build());
}
@ -654,8 +677,7 @@ public class BesuNodeFactory {
BesuNodeConfigurationBuilder builder =
createConfigurationBuilderWithStaticNodes(name, staticNodes);
final GenesisConfigurationFactory genesis = new GenesisConfigurationFactory();
final String genesisData = genesis.readGenesisFile(genesisPath);
final String genesisData = GenesisConfigurationFactory.readGenesisFile(genesisPath);
return builder
.devMode(false)

@ -40,26 +40,30 @@ import com.google.common.io.Resources;
public class GenesisConfigurationFactory {
public Optional<String> createCliqueGenesisConfig(
private GenesisConfigurationFactory() {
throw new IllegalStateException("Utility class");
}
public static Optional<String> createCliqueGenesisConfig(
final Collection<? extends RunnableNode> validators) {
final String template = readGenesisFile("/clique/clique.json");
return updateGenesisExtraData(
validators, template, CliqueExtraData::createGenesisExtraDataString);
}
public Optional<String> createIbft2GenesisConfig(
public static Optional<String> createIbft2GenesisConfig(
final Collection<? extends RunnableNode> validators) {
return createIbft2GenesisConfig(validators, "/ibft/ibft.json");
}
public Optional<String> createIbft2GenesisConfig(
public static Optional<String> createIbft2GenesisConfig(
final Collection<? extends RunnableNode> validators, final String genesisFile) {
final String template = readGenesisFile(genesisFile);
return updateGenesisExtraData(
validators, template, IbftExtraDataCodec::createGenesisExtraDataString);
}
public Optional<String> createIbft2GenesisConfigFilterBootnode(
public static Optional<String> createIbft2GenesisConfigFilterBootnode(
final Collection<? extends RunnableNode> validators, final String genesisFile) {
final String template = readGenesisFile(genesisFile);
final List<? extends RunnableNode> filteredList =
@ -70,14 +74,14 @@ public class GenesisConfigurationFactory {
filteredList, template, IbftExtraDataCodec::createGenesisExtraDataString);
}
public Optional<String> createPrivacyIbft2GenesisConfig(
public static Optional<String> createPrivacyIbft2GenesisConfig(
final Collection<? extends RunnableNode> validators) {
final String template = readGenesisFile("/ibft/privacy-ibft.json");
return updateGenesisExtraData(
validators, template, IbftExtraDataCodec::createGenesisExtraDataString);
}
public Optional<String> createQbftGenesisConfig(
public static Optional<String> createQbftGenesisConfig(
final Collection<? extends RunnableNode> validators) {
final String template = readGenesisFile("/qbft/qbft.json");
return updateGenesisExtraData(
@ -85,7 +89,7 @@ public class GenesisConfigurationFactory {
}
@SuppressWarnings("unchecked")
public Optional<String> createQbftValidatorContractGenesisConfig(
public static Optional<String> createQbftValidatorContractGenesisConfig(
final Collection<? extends RunnableNode> validators) throws UncheckedIOException {
final String template = readGenesisFile("/qbft/qbft-emptyextradata.json");
final String contractAddress = "0x0000000000000000000000000000000000008888";
@ -116,7 +120,14 @@ public class GenesisConfigurationFactory {
}
}
private Optional<String> updateGenesisExtraData(
public static Optional<String> createDevLondonGenesisConfig(
final Collection<? extends RunnableNode> validators) {
final String template = readGenesisFile("/dev/dev_london.json");
return updateGenesisExtraData(
validators, template, CliqueExtraData::createGenesisExtraDataString);
}
private static Optional<String> updateGenesisExtraData(
final Collection<? extends RunnableNode> validators,
final String genesisTemplate,
final Function<List<Address>, String> extraDataCreator) {
@ -128,9 +139,9 @@ public class GenesisConfigurationFactory {
}
@SuppressWarnings("UnstableApiUsage")
public String readGenesisFile(final String filepath) {
public static String readGenesisFile(final String filepath) {
try {
final URI uri = this.getClass().getResource(filepath).toURI();
final URI uri = GenesisConfigurationFactory.class.getResource(filepath).toURI();
return Resources.toString(uri.toURL(), Charset.defaultCharset());
} catch (final URISyntaxException | IOException e) {
throw new IllegalStateException("Unable to get test genesis config " + filepath);

@ -33,7 +33,6 @@ import org.testcontainers.containers.Network;
public class PrivacyNodeFactory {
private final GenesisConfigurationFactory genesis = new GenesisConfigurationFactory();
private final NodeConfigurationFactory node = new NodeConfigurationFactory();
private final Vertx vertx;
@ -160,7 +159,7 @@ public class PrivacyNodeFactory {
.jsonRpcConfiguration(node.createJsonRpcWithIbft2EnabledConfig(minerEnabled))
.webSocketConfiguration(node.createWebSocketEnabledConfig())
.devMode(false)
.genesisConfigProvider(genesis::createPrivacyIbft2GenesisConfig)
.genesisConfigProvider(GenesisConfigurationFactory::createPrivacyIbft2GenesisConfig)
.keyFilePath(privacyAccount.getPrivateKeyPath())
.enablePrivateTransactions()
.plugins(Collections.singletonList("testPlugins"))
@ -195,7 +194,7 @@ public class PrivacyNodeFactory {
.jsonRpcConfiguration(node.createJsonRpcWithIbft2EnabledConfig(minerEnabled))
.webSocketConfiguration(node.createWebSocketEnabledConfig())
.devMode(false)
.genesisConfigProvider(genesis::createPrivacyIbft2GenesisConfig)
.genesisConfigProvider(GenesisConfigurationFactory::createPrivacyIbft2GenesisConfig)
.keyFilePath(privacyAccount.getPrivateKeyPath())
.enablePrivateTransactions()
.plugins(Collections.singletonList("testPlugins"))
@ -231,7 +230,7 @@ public class PrivacyNodeFactory {
.jsonRpcConfiguration(node.createJsonRpcWithQbftEnabledConfig(false))
.webSocketConfiguration(node.createWebSocketEnabledConfig())
.devMode(false)
.genesisConfigProvider(genesis::createQbftGenesisConfig)
.genesisConfigProvider(GenesisConfigurationFactory::createQbftGenesisConfig)
.keyFilePath(privacyAccount.getPrivateKeyPath())
.enablePrivateTransactions()
.plugins(Collections.singletonList("testPlugins"))

@ -124,7 +124,8 @@ public class PrivacyNode implements AutoCloseable {
besuConfig.getPrivacyParameters(),
List.of(),
Optional.empty(),
Optional.empty());
Optional.empty(),
besuConfig.isStrictTxReplayProtectionEnabled());
}
public void testEnclaveConnection(final List<PrivacyNode> otherNodes) {

@ -19,26 +19,38 @@ import org.hyperledger.besu.crypto.SECPSignature;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.tests.acceptance.dsl.account.Account;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes32;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.RawTransaction;
import org.web3j.crypto.Sign;
import org.web3j.crypto.TransactionEncoder;
import org.web3j.crypto.transaction.type.TransactionType;
import org.web3j.rlp.RlpEncoder;
import org.web3j.rlp.RlpList;
import org.web3j.rlp.RlpString;
import org.web3j.rlp.RlpType;
import org.web3j.utils.Bytes;
import org.web3j.utils.Numeric;
public class SignUtil {
private static final int CHAIN_ID_INC = 35;
// In Ethereum transaction 27 is added to recId (v)
// See https://ethereum.github.io/yellowpaper/paper.pdf
// Appendix F. Signing Transactions (281)
private static final int LOWER_REAL_V = 27;
private SignUtil() {}
public static String signTransaction(
public static byte[] signTransaction(
final RawTransaction transaction,
final Account sender,
final SignatureAlgorithm signatureAlgorithm) {
final SignatureAlgorithm signatureAlgorithm,
final Optional<BigInteger> chainId) {
byte[] encodedTransaction = TransactionEncoder.encode(transaction);
Credentials credentials = sender.web3jCredentialsOrThrow();
@ -53,16 +65,53 @@ public class SignUtil {
Sign.SignatureData signature =
new Sign.SignatureData(
// In Ethereum transaction 27 is added to recId (v)
// See https://ethereum.github.io/yellowpaper/paper.pdf
// Appendix F. Signing Transactions (281)
(byte) (secpSignature.getRecId() + 27),
calculateV(secpSignature, chainId),
secpSignature.getR().toByteArray(),
secpSignature.getS().toByteArray());
List<RlpType> values = TransactionEncoder.asRlpValues(transaction, signature);
List<RlpType> values = getTxRlpValues(transaction, signature, secpSignature);
RlpList rlpList = new RlpList(values);
final byte[] encodedSignedTransaction = RlpEncoder.encode(rlpList);
byte[] encoded = RlpEncoder.encode(rlpList);
return Numeric.toHexString(encodedSignedTransaction);
if (transaction.getType().equals(TransactionType.LEGACY)) {
return encoded;
}
return ByteBuffer.allocate(encoded.length + 1)
.put(transaction.getType().getRlpType())
.put(encoded)
.array();
}
private static List<RlpType> getTxRlpValues(
final RawTransaction transaction,
final Sign.SignatureData signature,
final SECPSignature secpSignature) {
final List<RlpType> values = TransactionEncoder.asRlpValues(transaction, signature);
if (!transaction.getType().equals(TransactionType.EIP1559)) {
return values;
}
// Fix yParityField for eip1559 txs
// See outstanding fix: https://github.com/web3j/web3j/pull/1587
final int yParityFieldIndex = values.size() - 3;
byte recId = secpSignature.getRecId();
final byte[] yParityFieldValue =
recId == 0 ? new byte[] {} : new byte[] {secpSignature.getRecId()};
final RlpType yParityRlpString = RlpString.create(Bytes.trimLeadingZeroes(yParityFieldValue));
values.set(yParityFieldIndex, yParityRlpString);
return values;
}
private static byte[] calculateV(
final SECPSignature secpSignature, final Optional<BigInteger> maybeChainId) {
byte recId = secpSignature.getRecId();
return maybeChainId
.map(
chainId -> {
BigInteger v = Numeric.toBigInt(new byte[] {recId});
v = v.add(chainId.multiply(BigInteger.valueOf(2)));
v = v.add(BigInteger.valueOf(CHAIN_ID_INC));
return v.toByteArray();
})
.orElseGet(() -> new byte[] {(byte) (recId + LOWER_REAL_V)});
}
}

@ -1,23 +0,0 @@
/*
* 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.tests.acceptance.dsl.transaction;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
@FunctionalInterface
public interface TransactionWithSignatureAlgorithm<T> {
T execute(final NodeRequests node, final SignatureAlgorithm signatureAlgorithm);
}

@ -14,6 +14,7 @@
*/
package org.hyperledger.besu.tests.acceptance.dsl.transaction.account;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.tests.acceptance.dsl.account.Account;
import org.hyperledger.besu.tests.acceptance.dsl.account.Accounts;
import org.hyperledger.besu.tests.acceptance.dsl.blockchain.Amount;
@ -24,6 +25,7 @@ import java.util.List;
public class AccountTransactions {
private static final Amount DEFAULT_GAS_PRICE = Amount.wei(BigInteger.valueOf(10000000000L));
private final Accounts accounts;
public AccountTransactions(final Accounts accounts) {
@ -34,46 +36,44 @@ public class AccountTransactions {
return createTransfer(accounts.getPrimaryBenefactor(), recipient, amount);
}
public TransferTransaction createTransfer(
final Account recipient, final int amount, final SignatureAlgorithm signatureAlgorithm) {
return createBuilder(accounts.getPrimaryBenefactor(), recipient, Amount.ether(amount))
.setSignatureAlgorithm(signatureAlgorithm)
.build();
}
public TransferTransaction createTransfer(
final Account recipient, final int amount, final long chainId) {
return createBuilder(accounts.getPrimaryBenefactor(), recipient, Amount.ether(amount))
.chainId(chainId)
.build();
}
public TransferTransaction createTransfer(final Account recipient, final Amount amount) {
return createTransfer(accounts.getPrimaryBenefactor(), recipient, amount);
}
public TransferTransaction createTransfer(
final Account sender, final Account recipient, final int amount) {
return new TransferTransactionBuilder()
.sender(sender)
.recipient(recipient)
.amount(Amount.ether(amount))
.build();
return createBuilder(sender, recipient, Amount.ether(amount)).build();
}
public TransferTransaction createTransfer(
final Account sender, final Account recipient, final Amount amount) {
return new TransferTransactionBuilder()
.sender(sender)
.recipient(recipient)
.amount(amount)
.build();
return createBuilder(sender, recipient, amount).build();
}
public TransferTransaction createTransfer(
final Account sender, final Account recipient, final int amount, final BigInteger nonce) {
return new TransferTransactionBuilder()
.sender(sender)
.recipient(recipient)
.amount(Amount.ether(amount))
.nonce(nonce)
.build();
return createBuilder(sender, recipient, Amount.ether(amount)).nonce(nonce).build();
}
public TransferTransactionSet createIncrementalTransfers(
final Account sender, final Account recipient, final int etherAmount) {
final List<TransferTransaction> transfers = new ArrayList<>();
final TransferTransactionBuilder transferOneEther =
new TransferTransactionBuilder()
.sender(sender)
.recipient(recipient)
.amount(Amount.ether(1));
createBuilder(sender, recipient, Amount.ether(1));
for (int i = 1; i <= etherAmount; i++) {
transfers.add(transferOneEther.build());
@ -81,4 +81,13 @@ public class AccountTransactions {
return new TransferTransactionSet(transfers);
}
private TransferTransactionBuilder createBuilder(
final Account sender, final Account recipient, final Amount amount) {
return new TransferTransactionBuilder()
.sender(sender)
.recipient(recipient)
.amount(amount)
.gasPrice(DEFAULT_GAS_PRICE);
}
}

@ -21,7 +21,6 @@ import org.hyperledger.besu.tests.acceptance.dsl.blockchain.Amount;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.SignUtil;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.TransactionWithSignatureAlgorithm;
import java.io.IOException;
import java.math.BigDecimal;
@ -29,14 +28,12 @@ import java.math.BigInteger;
import java.util.Optional;
import org.web3j.crypto.RawTransaction;
import org.web3j.crypto.TransactionEncoder;
import org.web3j.protocol.core.methods.response.EthSendTransaction;
import org.web3j.utils.Convert;
import org.web3j.utils.Convert.Unit;
import org.web3j.utils.Numeric;
public class TransferTransaction
implements Transaction<Hash>, TransactionWithSignatureAlgorithm<Hash> {
public class TransferTransaction implements Transaction<Hash> {
/** Price for each for each GAS units in this transaction (wei). */
private static final BigInteger MINIMUM_GAS_PRICE = BigInteger.valueOf(1000);
@ -50,19 +47,26 @@ public class TransferTransaction
private final Unit transferUnit;
private final BigInteger gasPrice;
private final BigInteger nonce;
private final Optional<BigInteger> chainId;
private final SignatureAlgorithm signatureAlgorithm;
private byte[] signedTxData = null;
public TransferTransaction(
final Account sender,
final Account recipient,
final Amount transferAmount,
final Amount gasPrice,
final BigInteger nonce) {
final BigInteger nonce,
final Optional<BigInteger> chainId,
final SignatureAlgorithm signatureAlgorithm) {
this.sender = sender;
this.recipient = recipient;
this.transferAmount = transferAmount.getValue();
this.transferUnit = transferAmount.getUnit();
this.gasPrice = gasPrice == null ? MINIMUM_GAS_PRICE : convertGasPriceToWei(gasPrice);
this.nonce = nonce;
this.chainId = chainId;
this.signatureAlgorithm = signatureAlgorithm;
}
@Override
@ -71,27 +75,26 @@ public class TransferTransaction
return sendRawTransaction(node, signedTransactionData);
}
@Override
public Hash execute(final NodeRequests node, final SignatureAlgorithm signatureAlgorithm) {
final String signedTransactionData =
signedTransactionDataWithSignatureAlgorithm(signatureAlgorithm);
return sendRawTransaction(node, signedTransactionData);
}
public Amount executionCost() {
return Amount.wei(INTRINSIC_GAS.multiply(gasPrice));
}
public String signedTransactionData() {
final RawTransaction transaction = createRawTransaction();
return Numeric.toHexString(createSignedTransactionData());
}
return Numeric.toHexString(
TransactionEncoder.signMessage(transaction, sender.web3jCredentialsOrThrow()));
public String transactionHash() {
final byte[] signedTx = createSignedTransactionData();
final byte[] txHash = org.web3j.crypto.Hash.sha3(signedTx);
return Numeric.toHexString(txHash);
}
private String signedTransactionDataWithSignatureAlgorithm(
final SignatureAlgorithm signatureAlgorithm) {
return SignUtil.signTransaction(createRawTransaction(), sender, signatureAlgorithm);
private byte[] createSignedTransactionData() {
if (signedTxData == null) {
signedTxData =
SignUtil.signTransaction(createRawTransaction(), sender, signatureAlgorithm, chainId);
}
return signedTxData;
}
private Hash sendRawTransaction(final NodeRequests node, final String signedTransactionData) {
@ -108,8 +111,8 @@ public class TransferTransaction
}
}
private Optional<BigInteger> getNonce() {
return nonce == null ? Optional.empty() : Optional.of(nonce);
private BigInteger getNonce() {
return Optional.ofNullable(nonce).orElseGet(sender::getNextNonce);
}
private BigInteger convertGasPriceToWei(final Amount unconverted) {
@ -127,13 +130,28 @@ public class TransferTransaction
}
private RawTransaction createRawTransaction() {
final Optional<BigInteger> nonce = getNonce();
return chainId
.map(this::createTransactionWithChainId)
.orElseGet(this::createTransactionWithoutChainId);
}
private RawTransaction createTransactionWithoutChainId() {
return RawTransaction.createEtherTransaction(
nonce.orElse(nonce.orElseGet(sender::getNextNonce)),
getNonce(),
gasPrice,
INTRINSIC_GAS,
recipient.getAddress(),
Convert.toWei(transferAmount, transferUnit).toBigIntegerExact());
}
private RawTransaction createTransactionWithChainId(final BigInteger chainId) {
return RawTransaction.createEtherTransaction(
chainId.longValueExact(),
getNonce(),
INTRINSIC_GAS,
recipient.getAddress(),
Convert.toWei(transferAmount, transferUnit).toBigIntegerExact(),
BigInteger.ONE,
gasPrice);
}
}

@ -14,10 +14,15 @@
*/
package org.hyperledger.besu.tests.acceptance.dsl.transaction.account;
import static org.testcontainers.shaded.com.google.common.base.Preconditions.checkNotNull;
import org.hyperledger.besu.crypto.SECP256K1;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.tests.acceptance.dsl.account.Account;
import org.hyperledger.besu.tests.acceptance.dsl.blockchain.Amount;
import java.math.BigInteger;
import java.util.Optional;
public class TransferTransactionBuilder {
@ -26,6 +31,8 @@ public class TransferTransactionBuilder {
private Amount transferAmount;
private Amount gasPrice;
private BigInteger nonce;
private Optional<BigInteger> chainId = Optional.empty();
private SignatureAlgorithm signatureAlgorithm = new SECP256K1();
public TransferTransactionBuilder sender(final Account sender) {
this.sender = sender;
@ -54,10 +61,28 @@ public class TransferTransactionBuilder {
return this;
}
public TransferTransactionBuilder setSignatureAlgorithm(
final SignatureAlgorithm signatureAlgorithm) {
checkNotNull(signatureAlgorithm);
this.signatureAlgorithm = signatureAlgorithm;
return this;
}
public TransferTransaction build() {
validateSender();
validateTransferAmount();
return new TransferTransaction(sender, recipient, transferAmount, gasPrice, nonce);
return new TransferTransaction(
sender, recipient, transferAmount, gasPrice, nonce, chainId, signatureAlgorithm);
}
public TransferTransactionBuilder chainId(final BigInteger chainId) {
this.chainId = Optional.ofNullable(chainId);
return this;
}
public TransferTransactionBuilder chainId(final Long chainId) {
checkNotNull(chainId);
return chainId(BigInteger.valueOf(chainId));
}
private void validateSender() {

@ -36,7 +36,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.jetbrains.annotations.NotNull;
@ -186,8 +186,8 @@ public class BackupRoundTripAcceptanceTest extends AbstractPreexistingNodeTest {
}
@NotNull
private Function<BesuNodeConfigurationBuilder, BesuNodeConfigurationBuilder>
configureNodeCommands(final Path dataPath, final String... commands) {
private UnaryOperator<BesuNodeConfigurationBuilder> configureNodeCommands(
final Path dataPath, final String... commands) {
return nodeBuilder -> super.configureNode(nodeBuilder).dataPath(dataPath).run(commands);
}
}

@ -83,7 +83,7 @@ public class SECP256R1AcceptanceTest extends AcceptanceTestBase {
final Account recipient = accounts.createAccount("recipient");
final Hash transactionHash =
minerNode.execute(accountTransactions.createTransfer(recipient, 5), new SECP256R1());
minerNode.execute(accountTransactions.createTransfer(recipient, 5, new SECP256R1()));
assertThat(transactionHash).isNotNull();
cluster.verify(recipient.balanceEquals(5));
}

@ -0,0 +1,120 @@
/*
* 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.tests.acceptance.jsonrpc;
import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase;
import org.hyperledger.besu.tests.acceptance.dsl.account.Account;
import org.hyperledger.besu.tests.acceptance.dsl.node.Node;
import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.BesuNodeConfigurationBuilder;
import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.genesis.GenesisConfigurationFactory;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.account.TransferTransaction;
import java.math.BigInteger;
import java.util.function.UnaryOperator;
import org.junit.Before;
import org.junit.Test;
public class EthSendRawTransactionTest extends AcceptanceTestBase {
private static final long CHAIN_ID = 20211;
private Account sender;
private Node lenientNode;
private Node strictNode;
private Node miningNode;
@Before
public void setUp() throws Exception {
sender = accounts.getPrimaryBenefactor();
lenientNode = besu.createArchiveNode("lenientNode", configureNode((false)));
strictNode = besu.createArchiveNode("strictNode", configureNode((true)));
miningNode = besu.createMinerNode("strictMiningNode", configureNode((true)));
cluster.start(lenientNode, strictNode, miningNode);
}
@Test
public void shouldSendSuccessfullyToLenientNodeWithoutChainId() {
final TransferTransaction tx = createTransactionWithoutChainId();
final String rawTx = tx.signedTransactionData();
final String txHash = tx.transactionHash();
lenientNode.verify(eth.expectSuccessfulEthRawTransaction(rawTx));
// Tx should be included on-chain
miningNode.verify(eth.expectSuccessfulTransactionReceipt(txHash));
}
@Test
public void shouldFailToSendToToStrictNodeWithoutChainId() {
final TransferTransaction tx = createTransactionWithoutChainId();
final String rawTx = tx.signedTransactionData();
strictNode.verify(eth.expectEthSendRawTransactionException(rawTx, "ChainId is required"));
}
@Test
public void shouldSendSuccessfullyWithChainId_lenientNode() {
final TransferTransaction tx = createTransactionWithChainId();
final String rawTx = tx.signedTransactionData();
final String txHash = tx.transactionHash();
lenientNode.verify(eth.expectSuccessfulEthRawTransaction(rawTx));
// Tx should be included on-chain
miningNode.verify(eth.expectSuccessfulTransactionReceipt(txHash));
}
@Test
public void shouldSendSuccessfullyWithChainId_strictNode() {
final TransferTransaction tx = createTransactionWithChainId();
final String rawTx = tx.signedTransactionData();
final String txHash = tx.transactionHash();
strictNode.verify(eth.expectSuccessfulEthRawTransaction(rawTx));
// Tx should be included on-chain
miningNode.verify(eth.expectSuccessfulTransactionReceipt(txHash));
}
private TransferTransaction createTransactionWithChainId() {
return createTransaction(true);
}
private TransferTransaction createTransactionWithoutChainId() {
return createTransaction(false);
}
private TransferTransaction createTransaction(final boolean withChainId) {
if (withChainId) {
return accountTransactions.createTransfer(createAccount(), 2, CHAIN_ID);
} else {
final BigInteger nonce =
miningNode.execute(ethTransactions.getTransactionCount(sender.getAddress()));
return accountTransactions.createTransfer(
accounts.getPrimaryBenefactor(), createAccount(), 1, nonce);
}
}
private UnaryOperator<BesuNodeConfigurationBuilder> configureNode(
final boolean enableStrictReplayProtection) {
return b ->
b.genesisConfigProvider(GenesisConfigurationFactory::createDevLondonGenesisConfig)
.strictTxReplayProtectionEnabled(enableStrictReplayProtection)
.devMode(false);
}
private Account createAccount() {
return accounts.createAccount("Test account");
}
}

@ -114,7 +114,7 @@ public class AccountLocalAndOnchainPermissioningAcceptanceTest
final TransferTransaction transfer =
accountTransactions.createTransfer(sender, beneficiary, 1, nonce);
node.verify(
eth.sendRawTransactionExceptional(
eth.expectEthSendRawTransactionException(
transfer.signedTransactionData(),
"Sender account not authorized to send transactions"));
}

@ -78,7 +78,7 @@ public class AccountLocalConfigPermissioningAcceptanceTest extends AcceptanceTes
TransferTransaction transfer =
accountTransactions.createTransfer(sender, beneficiary, 1, nonce);
node.verify(
eth.sendRawTransactionExceptional(
eth.expectEthSendRawTransactionException(
transfer.signedTransactionData(),
"Sender account not authorized to send transactions"));
}

@ -70,7 +70,7 @@ public class AccountSmartContractPermissioningAcceptanceTest
final TransferTransaction transfer =
accountTransactions.createTransfer(sender, beneficiary, 1, nonce);
node.verify(
eth.sendRawTransactionExceptional(
eth.expectEthSendRawTransactionException(
transfer.signedTransactionData(),
"Sender account not authorized to send transactions"));
}

@ -0,0 +1,43 @@
{
"config":{
"chainId":20211,
"homesteadBlock":0,
"eip150Block":0,
"eip155Block":0,
"eip158Block":0,
"byzantiumBlock":0,
"constantinopleBlock":0,
"petersburgBlock":0,
"istanbulBlock":0,
"berlinBlock":0,
"londonBlock":0,
"ethash":{
"fixeddifficulty": 1
}
},
"alloc":{
"fe3b557e8fb62b89f4916b721be55ceb828dbd73": {
"privateKey": "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63",
"comment": "private key and this comment are ignored. In a real chain, the private key should NOT be stored",
"balance": "0xad78ebc5ac6200000"
},
"627306090abaB3A6e1400e9345bC60c78a8BEf57": {
"privateKey": "c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3",
"comment": "private key and this comment are ignored. In a real chain, the private key should NOT be stored",
"balance": "90000000000000000000000"
},
"f17f52151EbEF6C7334FAD080c5704D77216b732": {
"privateKey": "ae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f",
"comment": "private key and this comment are ignored. In a real chain, the private key should NOT be stored",
"balance": "90000000000000000000000"
}
},
"coinbase":"0x0000000000000000000000000000000000000000",
"difficulty":"0x00001",
"extraData":"0x5365706f6c69612c20417468656e732c204174746963612c2047726565636521",
"gasLimit":"0x1c9c380",
"nonce":"0x000000000000000",
"mixhash":"0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp":"0x6159af19"
}

@ -33,6 +33,18 @@ public class TransactionPoolOptions
private static final String ETH65_TX_ANNOUNCED_BUFFERING_PERIOD_FLAG =
"--Xeth65-tx-announced-buffering-period-milliseconds";
private static final String STRICT_TX_REPLAY_PROTECTION_ENABLED_FLAG =
"--strict-tx-replay-protection-enabled";
@CommandLine.Option(
names = {STRICT_TX_REPLAY_PROTECTION_ENABLED_FLAG},
paramLabel = "<Boolean>",
description =
"Require transactions submitted via JSON-RPC to use replay protection in accordance with EIP-155 (default: ${DEFAULT-VALUE})",
fallbackValue = "true",
arity = "0..1")
private Boolean strictTxReplayProtectionEnabled = false;
@CommandLine.Option(
names = {TX_MESSAGE_KEEP_ALIVE_SEC_FLAG},
paramLabel = "<INTEGER>",
@ -64,12 +76,14 @@ public class TransactionPoolOptions
options.txMessageKeepAliveSeconds = config.getTxMessageKeepAliveSeconds();
options.eth65TrxAnnouncedBufferingPeriod =
config.getEth65TrxAnnouncedBufferingPeriod().toMillis();
options.strictTxReplayProtectionEnabled = config.getStrictTransactionReplayProtectionEnabled();
return options;
}
@Override
public ImmutableTransactionPoolConfiguration.Builder toDomainObject() {
return ImmutableTransactionPoolConfiguration.builder()
.strictTransactionReplayProtectionEnabled(strictTxReplayProtectionEnabled)
.txMessageKeepAliveSeconds(txMessageKeepAliveSeconds)
.eth65TrxAnnouncedBufferingPeriod(Duration.ofMillis(eth65TrxAnnouncedBufferingPeriod));
}
@ -77,6 +91,7 @@ public class TransactionPoolOptions
@Override
public List<String> getCLIOptions() {
return Arrays.asList(
STRICT_TX_REPLAY_PROTECTION_ENABLED_FLAG + "=" + strictTxReplayProtectionEnabled,
TX_MESSAGE_KEEP_ALIVE_SEC_FLAG,
OptionParser.format(txMessageKeepAliveSeconds),
ETH65_TX_ANNOUNCED_BUFFERING_PERIOD_FLAG,

@ -29,6 +29,54 @@ public class TransactionPoolOptionsTest
extends AbstractCLIOptionsTest<
ImmutableTransactionPoolConfiguration.Builder, TransactionPoolOptions> {
@Test
public void strictTxReplayProtection_enabled() {
final TestBesuCommand cmd = parseCommand("--strict-tx-replay-protection-enabled");
final TransactionPoolOptions options = getOptionsFromBesuCommand(cmd);
final TransactionPoolConfiguration config = options.toDomainObject().build();
assertThat(config.getStrictTransactionReplayProtectionEnabled()).isTrue();
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void strictTxReplayProtection_enabledWithBooleanArg() {
final TestBesuCommand cmd = parseCommand("--strict-tx-replay-protection-enabled=true");
final TransactionPoolOptions options = getOptionsFromBesuCommand(cmd);
final TransactionPoolConfiguration config = options.toDomainObject().build();
assertThat(config.getStrictTransactionReplayProtectionEnabled()).isTrue();
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void strictTxReplayProtection_disabled() {
final TestBesuCommand cmd = parseCommand("--strict-tx-replay-protection-enabled=false");
final TransactionPoolOptions options = getOptionsFromBesuCommand(cmd);
final TransactionPoolConfiguration config = options.toDomainObject().build();
assertThat(config.getStrictTransactionReplayProtectionEnabled()).isFalse();
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void strictTxReplayProtection_default() {
final TestBesuCommand cmd = parseCommand();
final TransactionPoolOptions options = getOptionsFromBesuCommand(cmd);
final TransactionPoolConfiguration config = options.toDomainObject().build();
assertThat(config.getStrictTransactionReplayProtectionEnabled()).isFalse();
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void txMessageKeepAliveSeconds() {
final int txMessageKeepAliveSeconds = 999;
@ -67,6 +115,7 @@ public class TransactionPoolOptionsTest
final ImmutableTransactionPoolConfiguration defaultValue =
ImmutableTransactionPoolConfiguration.builder().build();
return ImmutableTransactionPoolConfiguration.builder()
.strictTransactionReplayProtectionEnabled(false)
.txMessageKeepAliveSeconds(defaultValue.getTxMessageKeepAliveSeconds())
.eth65TrxAnnouncedBufferingPeriod(defaultValue.getEth65TrxAnnouncedBufferingPeriod());
}
@ -74,6 +123,7 @@ public class TransactionPoolOptionsTest
@Override
ImmutableTransactionPoolConfiguration.Builder createCustomizedDomainObject() {
return ImmutableTransactionPoolConfiguration.builder()
.strictTransactionReplayProtectionEnabled(true)
.txMessageKeepAliveSeconds(TransactionPoolConfiguration.DEFAULT_TX_MSG_KEEP_ALIVE + 1)
.eth65TrxAnnouncedBufferingPeriod(
TransactionPoolConfiguration.ETH65_TRX_ANNOUNCED_BUFFERING_PERIOD.plus(

@ -159,6 +159,7 @@ tx-pool-max-size=1234
tx-pool-hashes-max-size=10000
Xincoming-tx-messages-keep-alive-seconds=60
rpc-tx-feecap=2000000000000000000
strict-tx-replay-protection-enabled=true
# Revert Reason
revert-reason-enabled=false

@ -40,6 +40,8 @@ public class JsonRpcErrorConverter {
return JsonRpcError.WRONG_CHAIN_ID;
case REPLAY_PROTECTED_SIGNATURES_NOT_SUPPORTED:
return JsonRpcError.REPLAY_PROTECTED_SIGNATURES_NOT_SUPPORTED;
case REPLAY_PROTECTED_SIGNATURE_REQUIRED:
return JsonRpcError.REPLAY_PROTECTED_SIGNATURE_REQUIRED;
case TX_SENDER_NOT_AUTHORIZED:
return JsonRpcError.TX_SENDER_NOT_AUTHORIZED;
// Private Transaction Invalid Reasons

@ -61,6 +61,7 @@ public enum JsonRpcError {
GAS_PRICE_TOO_LOW(-32009, "Gas price below configured minimum gas price"),
WRONG_CHAIN_ID(-32000, "Wrong chainId"),
REPLAY_PROTECTED_SIGNATURES_NOT_SUPPORTED(-32000, "ChainId not supported"),
REPLAY_PROTECTED_SIGNATURE_REQUIRED(-32000, "ChainId is required"),
TX_FEECAP_EXCEEDED(-32000, "Transaction fee cap exceeded"),
REVERT_ERROR(-32000, "Execution reverted"),
GAS_PRICE_MUST_BE_ZERO(-3200, "gasPrice must be set to zero on a GoQuorum compatible network"),

@ -222,6 +222,10 @@ public class MainnetTransactionValidator {
return ValidationResult.valid();
}
public boolean isReplayProtectionSupported() {
return chainId.isPresent();
}
public ValidationResult<TransactionInvalidReason> validateTransactionSignature(
final Transaction transaction) {
if (chainId.isPresent()

@ -170,6 +170,10 @@ public class ProtocolSpec {
return transactionValidator;
}
public boolean isReplayProtectionSupported() {
return transactionValidator.isReplayProtectionSupported();
}
/**
* Returns the transaction processor used in this specification.
*

@ -20,6 +20,7 @@ package org.hyperledger.besu.ethereum.transaction;
public enum TransactionInvalidReason {
WRONG_CHAIN_ID,
REPLAY_PROTECTED_SIGNATURES_NOT_SUPPORTED,
REPLAY_PROTECTED_SIGNATURE_REQUIRED,
INVALID_SIGNATURE,
UPFRONT_COST_EXCEEDS_BALANCE,
NONCE_TOO_LOW,

@ -132,7 +132,7 @@ public class TransactionPool implements BlockAddedObserver {
public ValidationResult<TransactionInvalidReason> addLocalTransaction(
final Transaction transaction) {
final ValidationResult<TransactionInvalidReason> validationResult =
validateTransaction(transaction);
validateLocalTransaction(transaction);
if (validationResult.isValid()) {
if (!configuration.getTxFeeCap().isZero()
&& minTransactionGasPrice(transaction).compareTo(configuration.getTxFeeCap()) > 0) {
@ -169,7 +169,7 @@ public class TransactionPool implements BlockAddedObserver {
continue;
}
final ValidationResult<TransactionInvalidReason> validationResult =
validateTransaction(transaction);
validateRemoteTransaction(transaction);
if (validationResult.isValid()) {
final boolean added = pendingTransactions.addRemoteTransaction(transaction);
if (added) {
@ -222,8 +222,18 @@ public class TransactionPool implements BlockAddedObserver {
return pendingTransactions;
}
private ValidationResult<TransactionInvalidReason> validateTransaction(
private ValidationResult<TransactionInvalidReason> validateLocalTransaction(
final Transaction transaction) {
return validateTransaction(transaction, true);
}
private ValidationResult<TransactionInvalidReason> validateRemoteTransaction(
final Transaction transaction) {
return validateTransaction(transaction, false);
}
private ValidationResult<TransactionInvalidReason> validateTransaction(
final Transaction transaction, final boolean isLocal) {
final BlockHeader chainHeadBlockHeader = getChainHeadBlockHeader();
// Check whether it's a GoQuorum transaction
@ -245,6 +255,12 @@ public class TransactionPool implements BlockAddedObserver {
return basicValidationResult;
}
if (isLocal
&& strictReplayProtectionShouldBeEnforceLocally(chainHeadBlockHeader)
&& transaction.getChainId().isEmpty()) {
// Strict replay protection is enabled but the tx is not replay-protected
return ValidationResult.invalid(TransactionInvalidReason.REPLAY_PROTECTED_SIGNATURE_REQUIRED);
}
if (transaction.getGasLimit() > chainHeadBlockHeader.getGasLimit()) {
return ValidationResult.invalid(
TransactionInvalidReason.EXCEEDS_BLOCK_GAS_LIMIT,
@ -274,6 +290,17 @@ public class TransactionPool implements BlockAddedObserver {
.orElseGet(() -> ValidationResult.invalid(CHAIN_HEAD_WORLD_STATE_NOT_AVAILABLE));
}
private boolean strictReplayProtectionShouldBeEnforceLocally(
final BlockHeader chainHeadBlockHeader) {
return configuration.getStrictTransactionReplayProtectionEnabled()
&& protocolSchedule.getChainId().isPresent()
&& transactionReplaySupportedAtBlock(chainHeadBlockHeader);
}
private boolean transactionReplaySupportedAtBlock(final BlockHeader block) {
return protocolSchedule.getByBlockNumber(block.getNumber()).isReplayProtectionSupported();
}
public Optional<Transaction> getTransactionByHash(final Hash hash) {
return pendingTransactions.getTransactionByHash(hash);
}

@ -28,6 +28,7 @@ public interface TransactionPoolConfiguration {
int MAX_PENDING_TRANSACTIONS = 4096;
int MAX_PENDING_TRANSACTIONS_HASHES = 4096;
int DEFAULT_TX_RETENTION_HOURS = 13;
boolean DEFAULT_STRICT_TX_REPLAY_PROTECTION_ENABLED = false;
Percentage DEFAULT_PRICE_BUMP = Percentage.fromInt(10);
Wei DEFAULT_RPC_TX_FEE_CAP = Wei.fromEth(1);
Duration ETH65_TRX_ANNOUNCED_BUFFERING_PERIOD = Duration.ofMillis(500);
@ -68,4 +69,9 @@ public interface TransactionPoolConfiguration {
default Wei getTxFeeCap() {
return DEFAULT_RPC_TX_FEE_CAP;
}
@Value.Default
default Boolean getStrictTransactionReplayProtectionEnabled() {
return DEFAULT_STRICT_TX_REPLAY_PROTECTION_ENABLED;
}
}

@ -89,7 +89,8 @@ public class TransactionPoolFactoryTest {
1,
TransactionPoolConfiguration.DEFAULT_PRICE_BUMP,
TransactionPoolConfiguration.ETH65_TRX_ANNOUNCED_BUFFERING_PERIOD,
TransactionPoolConfiguration.DEFAULT_RPC_TX_FEE_CAP),
TransactionPoolConfiguration.DEFAULT_RPC_TX_FEE_CAP,
TransactionPoolConfiguration.DEFAULT_STRICT_TX_REPLAY_PROTECTION_ENABLED),
pendingTransactions,
peerTransactionTracker,
transactionsMessageSender,

@ -78,6 +78,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import org.junit.Before;
import org.junit.Test;
@ -147,23 +148,36 @@ public class TransactionPoolTest {
when(ethContext.getEthPeers()).thenReturn(ethPeers);
peerTransactionTracker = mock(PeerTransactionTracker.class);
peerPendingTransactionTracker = mock(PeerPendingTransactionTracker.class);
transactionPool =
new TransactionPool(
transactions,
protocolSchedule,
protocolContext,
batchAddedListener,
pendingBatchAddedListener,
syncState,
ethContext,
peerTransactionTracker,
peerPendingTransactionTracker,
Wei.of(2),
metricsSystem,
TransactionPoolConfiguration.DEFAULT);
transactionPool = createTransactionPool();
blockchain.observeBlockAdded(transactionPool);
}
private TransactionPool createTransactionPool() {
return createTransactionPool(b -> {});
}
private TransactionPool createTransactionPool(
final Consumer<ImmutableTransactionPoolConfiguration.Builder> configConsumer) {
final ImmutableTransactionPoolConfiguration.Builder configBuilder =
ImmutableTransactionPoolConfiguration.builder();
configConsumer.accept(configBuilder);
final TransactionPoolConfiguration config = configBuilder.build();
return new TransactionPool(
transactions,
protocolSchedule,
protocolContext,
batchAddedListener,
pendingBatchAddedListener,
syncState,
ethContext,
peerTransactionTracker,
peerPendingTransactionTracker,
Wei.of(2),
metricsSystem,
config);
}
@Test
public void mainNetValueTransferSucceeds() {
final Transaction transaction =
@ -305,6 +319,101 @@ public class TransactionPoolTest {
assertTransactionPending(transaction2);
}
@Test
public void addLocalTransaction_strictReplayProtectionOn_txWithoutChainId_chainIdIsConfigured() {
protocolSupportsTxReplayProtection(1337, true);
transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(true));
final Transaction tx = createTransactionWithoutChainId(1);
givenTransactionIsValid(tx);
assertLocalTransactionInvalid(tx, TransactionInvalidReason.REPLAY_PROTECTED_SIGNATURE_REQUIRED);
}
@Test
public void
addLocalTransaction_strictReplayProtectionOn_txWithoutChainId_chainIdIsConfigured_protectionNotSupportedAtCurrentBlock() {
protocolSupportsTxReplayProtection(1337, false);
transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(true));
final Transaction tx = createTransactionWithoutChainId(1);
givenTransactionIsValid(tx);
assertLocalTransactionValid(tx);
}
@Test
public void addLocalTransaction_strictReplayProtectionOff_txWithoutChainId_chainIdIsConfigured() {
protocolSupportsTxReplayProtection(1337, true);
transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(false));
final Transaction tx = createTransactionWithoutChainId(1);
givenTransactionIsValid(tx);
assertLocalTransactionValid(tx);
}
@Test
public void
addLocalTransaction_strictReplayProtectionOn_txWithoutChainId_chainIdIsNotConfigured() {
protocolDoesNotSupportTxReplayProtection();
transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(true));
final Transaction tx = createTransactionWithoutChainId(1);
givenTransactionIsValid(tx);
assertLocalTransactionValid(tx);
}
@Test
public void addLocalTransaction_strictReplayProtectionOn_txWithChainId_chainIdIsConfigured() {
protocolSupportsTxReplayProtection(1337, true);
transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(true));
final Transaction tx = createTransaction(1);
givenTransactionIsValid(tx);
assertLocalTransactionValid(tx);
}
@Test
public void
addRemoteTransactions_strictReplayProtectionOn_txWithoutChainId_chainIdIsConfigured() {
protocolSupportsTxReplayProtection(1337, true);
transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(true));
final Transaction tx = createTransactionWithoutChainId(1);
givenTransactionIsValid(tx);
assertRemoteTransactionValid(tx);
}
@Test
public void
addRemoteTransactions_strictReplayProtectionOff_txWithoutChainId_chainIdIsConfigured() {
protocolSupportsTxReplayProtection(1337, true);
transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(false));
final Transaction tx = createTransactionWithoutChainId(1);
givenTransactionIsValid(tx);
assertRemoteTransactionValid(tx);
}
@Test
public void
addRemoteTransactions_strictReplayProtectionOn_txWithoutChainId_chainIdIsNotConfigured() {
protocolDoesNotSupportTxReplayProtection();
transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(true));
final Transaction tx = createTransactionWithoutChainId(1);
givenTransactionIsValid(tx);
assertRemoteTransactionValid(tx);
}
@Test
public void addRemoteTransactions_strictReplayProtectionOn_txWithChainId_chainIdIsConfigured() {
protocolSupportsTxReplayProtection(1337, true);
transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(true));
final Transaction tx = createTransaction(1);
givenTransactionIsValid(tx);
assertRemoteTransactionValid(tx);
}
@Test
public void shouldNotAddRemoteTransactionsWhenGasPriceBelowMinimum() {
final Transaction transaction =
@ -925,6 +1034,24 @@ public class TransactionPoolTest {
.createTransaction(KEY_PAIR1);
}
private Transaction createTransactionWithoutChainId(final int transactionNumber) {
return new TransactionTestFixture()
.chainId(Optional.empty())
.nonce(transactionNumber)
.gasLimit(0)
.createTransaction(KEY_PAIR1);
}
private void protocolDoesNotSupportTxReplayProtection() {
when(protocolSchedule.getChainId()).thenReturn(Optional.empty());
}
private void protocolSupportsTxReplayProtection(
final long chainId, final boolean isSupportedAtCurrentBlock) {
when(protocolSpec.isReplayProtectionSupported()).thenReturn(isSupportedAtCurrentBlock);
when(protocolSchedule.getChainId()).thenReturn(Optional.of(BigInteger.valueOf(chainId)));
}
private void givenTransactionIsValid(final Transaction transaction) {
when(transactionValidator.validate(eq(transaction), any(Optional.class), any()))
.thenReturn(valid());
@ -932,4 +1059,29 @@ public class TransactionPoolTest {
eq(transaction), nullable(Account.class), any(TransactionValidationParams.class)))
.thenReturn(valid());
}
private void assertLocalTransactionInvalid(
final Transaction tx, final TransactionInvalidReason invalidReason) {
final ValidationResult<TransactionInvalidReason> result =
transactionPool.addLocalTransaction(tx);
assertThat(result.isValid()).isFalse();
assertThat(result.getInvalidReason()).isEqualTo(invalidReason);
assertTransactionNotPending(tx);
}
private void assertLocalTransactionValid(final Transaction tx) {
final ValidationResult<TransactionInvalidReason> result =
transactionPool.addLocalTransaction(tx);
assertThat(result.isValid()).isTrue();
assertTransactionPending(tx);
}
private void assertRemoteTransactionValid(final Transaction tx) {
transactionPool.addRemoteTransactions(List.of(tx));
verify(batchAddedListener).onTransactionsAdded(singleton(tx));
assertTransactionPending(tx);
}
}

Loading…
Cancel
Save