diff --git a/CHANGELOG.md b/CHANGELOG.md index 590190d5354..a37d8282e06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,27 @@ # Changelog -## 22.7.1 +## 22.7.2 +### Besu 22.7.2 is a recommended release for the Merge and Mainnet users. 22.7.1 remains Merge-ready. This release provides additional robustness before the Merge with some fixes and improvements in sync, peering, and logging. + +### Additions and Improvements +- Better management of jemalloc presence/absence in startup script [#4237](https://github.com/hyperledger/besu/pull/4237) +- Retry mechanism when getting a broadcasted block fail on all peers [#4271](https://github.com/hyperledger/besu/pull/4271) +- Filter out disconnected peers when fetching available peers [#4269](https://github.com/hyperledger/besu/pull/4269) +- Updated the default value of fast-sync-min-peers post merge [#4298](https://github.com/hyperledger/besu/pull/4298) +- Log imported block info post merge [#4310](https://github.com/hyperledger/besu/pull/4310) +- Transaction pool eviction by sender from tail of transaction list [#4327](https://github.com/hyperledger/besu/pull/4327) +- Transaction pool sender future nonce limits [#4336](https://github.com/hyperledger/besu/pull/4336) +- Pandas! Pandas now appear in 3 phases: The black bear and polar bear that are preparing? Those will appear when +your client has TTD configured (which is setup by default for mainnet), is in sync, and processing Proof of Work blocks. In the second phase you will see them powering up when the Terminal Total Difficulty block is added to the blockchain. +The final form of the Ethereum Panda will appear when the first finalized block is received from the Consensus Layer. + +### Bug Fixes +- Accept wit/80 from Nethermind [#4279](https://github.com/hyperledger/besu/pull/4279) +- Properly shutdown the miner executor, to avoid waiting 30 seconds when stopping [#4353](https://github.com/hyperledger/besu/pull/4353) + + +## 22.7.1 + ### Merge Ready Release. Required update for The Merge on ethereum mainnet! ### Additions and Improvements @@ -16,12 +37,16 @@ - Fix encoding of key (short hex) in eth_getProof [#4261](https://github.com/hyperledger/besu/pull/4261) - Fix for post-merge networks fast-sync [#4224](https://github.com/hyperledger/besu/pull/4224), [#4276](https://github.com/hyperledger/besu/pull/4276) +### Download links +- https://hyperledger.jfrog.io/artifactory/besu-binaries/besu/22.7.1/besu-22.7.1.tar.gz / sha256: `7cca4c11e1d7525c172f2af9fbf456d134ada60e970d8b6abcfcd6c623b5dd36` +- https://hyperledger.jfrog.io/artifactory/besu-binaries/besu/22.7.1/besu-22.7.1.zip / sha256: `ba6e0b9b65ac36d041a5072392f119ff76e8e9f53a3d7b1e1a658ef1e4705d7a` + ## 22.7.0 ### Additions and Improvements - Deprecation warning for Ropsten, Rinkeby, Kiln [#4173](https://github.com/hyperledger/besu/pull/4173) -### Bug Fixes +### Bug Fixes - Fixes previous known issue [#3890](https://github.com/hyperledger/besu/issues/3890)from RC3 requiring a restart post-merge to continue correct transaction handling. - Stop producing stack traces when a get headers response only contains the range start header [#4189](https://github.com/hyperledger/besu/pull/4189) @@ -40,8 +65,8 @@ ### Additions and Improvements - Engine API: Change expiration time for JWT tokens to 60s [#4168](https://github.com/hyperledger/besu/pull/4168) - Sepolia mergeNetSplit block [#4158](https://github.com/hyperledger/besu/pull/4158) -- Goerli TTD [#4160](https://github.com/hyperledger/besu/pull/4160) -- Several logging improvements +- Goerli TTD [#4160](https://github.com/hyperledger/besu/pull/4160) +- Several logging improvements ### Bug Fixes - Allow to set any value for baseFeePerGas in the genesis file [#4177](https://github.com/hyperledger/besu/pull/4177) diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/BftPrivacyClusterAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/BftPrivacyClusterAcceptanceTest.java index 2baf1d1f5ce..e2672a86d6c 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/BftPrivacyClusterAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/BftPrivacyClusterAcceptanceTest.java @@ -30,6 +30,7 @@ import java.util.Optional; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -38,6 +39,7 @@ import org.web3j.utils.Restriction; @RunWith(Parameterized.class) +@Ignore("Ignored since Tessera/Docker container startup causing errors") public class BftPrivacyClusterAcceptanceTest extends PrivacyAcceptanceTestBase { private final BftPrivacyType bftPrivacyType; diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/DeployPrivateSmartContractAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/DeployPrivateSmartContractAcceptanceTest.java index 71aa618cdd8..794a1e00883 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/DeployPrivateSmartContractAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/DeployPrivateSmartContractAcceptanceTest.java @@ -26,9 +26,11 @@ import java.io.IOException; import java.util.Optional; +import org.junit.Ignore; import org.junit.Test; import org.web3j.utils.Restriction; +@Ignore("Ignored since Tessera/Docker container startup causing errors") public class DeployPrivateSmartContractAcceptanceTest extends ParameterizedEnclaveTestBase { private final PrivacyNode minerNode; diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/EnclaveErrorAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/EnclaveErrorAcceptanceTest.java index 7822768f140..61fcceac934 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/EnclaveErrorAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/EnclaveErrorAcceptanceTest.java @@ -49,6 +49,7 @@ import org.web3j.utils.Restriction; @RunWith(Parameterized.class) +@Ignore("Ignored since Tessera/Docker container startup causing errors") public class EnclaveErrorAcceptanceTest extends PrivacyAcceptanceTestBase { private final PrivacyNode alice; diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/FlexiblePrivacyAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/FlexiblePrivacyAcceptanceTest.java index d64f73d2a03..362b26c56a4 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/FlexiblePrivacyAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/FlexiblePrivacyAcceptanceTest.java @@ -38,6 +38,7 @@ import com.google.common.collect.Lists; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -50,6 +51,7 @@ import org.web3j.tx.Contract; @RunWith(Parameterized.class) +@Ignore("Ignored since Tessera/Docker container startup causing errors") public class FlexiblePrivacyAcceptanceTest extends FlexiblePrivacyAcceptanceTestBase { private final EnclaveType enclaveType; diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivCallAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivCallAcceptanceTest.java index b2c9c3bda54..f073872686a 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivCallAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivCallAcceptanceTest.java @@ -33,6 +33,7 @@ import java.util.Optional; import javax.annotation.Nonnull; +import org.junit.Ignore; import org.junit.Test; import org.web3j.abi.FunctionEncoder; import org.web3j.abi.TypeReference; @@ -48,6 +49,7 @@ import org.web3j.tx.Contract; import org.web3j.utils.Restriction; +@Ignore("Ignored since Tessera/Docker container startup causing errors") public class PrivCallAcceptanceTest extends ParameterizedEnclaveTestBase { private static final int VALUE = 1024; diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivDebugGetStateRootFlexibleGroupAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivDebugGetStateRootFlexibleGroupAcceptanceTest.java index 59973fd00be..9681ec60e8c 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivDebugGetStateRootFlexibleGroupAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivDebugGetStateRootFlexibleGroupAcceptanceTest.java @@ -32,6 +32,7 @@ import org.apache.tuweni.bytes.Bytes32; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -39,6 +40,7 @@ import org.testcontainers.containers.Network; @RunWith(Parameterized.class) +@Ignore("Ignored since Tessera/Docker container startup causing errors") public class PrivDebugGetStateRootFlexibleGroupAcceptanceTest extends FlexiblePrivacyAcceptanceTestBase { diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivDebugGetStateRootOffchainGroupAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivDebugGetStateRootOffchainGroupAcceptanceTest.java index 92643446ddb..2d1fd76ecc4 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivDebugGetStateRootOffchainGroupAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivDebugGetStateRootOffchainGroupAcceptanceTest.java @@ -29,10 +29,12 @@ import java.util.Optional; import org.apache.tuweni.bytes.Bytes32; +import org.junit.Ignore; import org.junit.Test; import org.testcontainers.containers.Network; import org.web3j.utils.Restriction; +@Ignore("Ignored since Tessera/Docker container startup causing errors") public class PrivDebugGetStateRootOffchainGroupAcceptanceTest extends ParameterizedEnclaveTestBase { private final PrivacyNode aliceNode; diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivGetCodeAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivGetCodeAcceptanceTest.java index c08006f694b..640f34639b1 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivGetCodeAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivGetCodeAcceptanceTest.java @@ -29,9 +29,11 @@ import java.util.Optional; import org.apache.tuweni.bytes.Bytes; +import org.junit.Ignore; import org.junit.Test; import org.web3j.utils.Restriction; +@Ignore("Ignored since Tessera/Docker container startup causing errors") public class PrivGetCodeAcceptanceTest extends ParameterizedEnclaveTestBase { private final PrivacyNode alice; diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivGetLogsAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivGetLogsAcceptanceTest.java index 62519385a2e..e627206592f 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivGetLogsAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivGetLogsAcceptanceTest.java @@ -30,12 +30,14 @@ import java.util.List; import java.util.Optional; +import org.junit.Ignore; import org.junit.Test; import org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt; import org.web3j.protocol.core.methods.response.EthLog.LogResult; import org.web3j.utils.Restriction; @SuppressWarnings("rawtypes") +@Ignore("Ignored since Tessera/Docker container startup causing errors") public class PrivGetLogsAcceptanceTest extends ParameterizedEnclaveTestBase { /* diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivGetPrivateTransactionAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivGetPrivateTransactionAcceptanceTest.java index 91896761d09..0abac7bb9c5 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivGetPrivateTransactionAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivGetPrivateTransactionAcceptanceTest.java @@ -32,10 +32,12 @@ import java.util.Optional; import org.apache.tuweni.bytes.Bytes; +import org.junit.Ignore; import org.junit.Test; import org.testcontainers.containers.Network; import org.web3j.utils.Restriction; +@Ignore("Ignored since Tessera/Docker container startup causing errors") public class PrivGetPrivateTransactionAcceptanceTest extends ParameterizedEnclaveTestBase { private final PrivacyNode alice; diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivacyClusterAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivacyClusterAcceptanceTest.java index 1284daf917e..0386e29a4dc 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivacyClusterAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivacyClusterAcceptanceTest.java @@ -42,6 +42,7 @@ import io.vertx.core.Vertx; import org.apache.tuweni.bytes.Bytes; import org.junit.After; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -57,6 +58,7 @@ import org.web3j.utils.Numeric; @RunWith(Parameterized.class) +@Ignore("Ignored since Tessera/Docker container startup causing errors") public class PrivacyClusterAcceptanceTest extends PrivacyAcceptanceTestBase { private final PrivacyNode alice; diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivacyGroupAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivacyGroupAcceptanceTest.java index ab1bd91d0e7..6d6b4b6c3c6 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivacyGroupAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivacyGroupAcceptanceTest.java @@ -35,6 +35,7 @@ import java.util.Optional; import org.apache.logging.log4j.Level; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -45,6 +46,7 @@ import org.web3j.utils.Base64String; @RunWith(Parameterized.class) +@Ignore("Ignored since Tessera/Docker container startup causing errors") public class PrivacyGroupAcceptanceTest extends PrivacyAcceptanceTestBase { private final PrivacyNode alice; diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivacyReceiptAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivacyReceiptAcceptanceTest.java index 0b45d29732a..f4199ef11c4 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivacyReceiptAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivacyReceiptAcceptanceTest.java @@ -34,9 +34,11 @@ import java.util.Optional; import org.apache.tuweni.bytes.Bytes; +import org.junit.Ignore; import org.junit.Test; import org.web3j.utils.Restriction; +@Ignore("Ignored since Tessera/Docker container startup causing errors") public class PrivacyReceiptAcceptanceTest extends ParameterizedEnclaveTestBase { final MinerTransactions minerTransactions = new MinerTransactions(); diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivateContractPublicStateAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivateContractPublicStateAcceptanceTest.java index 83a7ce66d8b..9c03b4c3afd 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivateContractPublicStateAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivateContractPublicStateAcceptanceTest.java @@ -33,6 +33,7 @@ import java.math.BigInteger; import java.util.Optional; +import org.junit.Ignore; import org.junit.Test; import org.testcontainers.containers.Network; import org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt; @@ -42,6 +43,7 @@ import org.web3j.tx.exceptions.ContractCallException; import org.web3j.utils.Restriction; +@Ignore("Ignored since Tessera/Docker container startup causing errors") public class PrivateContractPublicStateAcceptanceTest extends ParameterizedEnclaveTestBase { private final PrivacyNode transactionNode; diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivateGenesisAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivateGenesisAcceptanceTest.java index 29d5b655e3c..dea263883b4 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivateGenesisAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivateGenesisAcceptanceTest.java @@ -29,6 +29,7 @@ import java.math.BigInteger; import java.util.Optional; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -36,6 +37,7 @@ import org.web3j.utils.Restriction; @RunWith(Parameterized.class) +@Ignore("Ignored since Tessera/Docker container startup causing errors") public class PrivateGenesisAcceptanceTest extends ParameterizedEnclaveTestBase { private final PrivacyNode alice; diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivateLogFilterAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivateLogFilterAcceptanceTest.java index a6837f95465..d2301d0de2c 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivateLogFilterAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivateLogFilterAcceptanceTest.java @@ -31,12 +31,14 @@ import java.util.List; import java.util.Optional; +import org.junit.Ignore; import org.junit.Test; import org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt; import org.web3j.protocol.core.methods.response.EthLog.LogResult; import org.web3j.utils.Restriction; @SuppressWarnings("rawtypes") +@Ignore("Ignored since Tessera/Docker container startup causing errors") public class PrivateLogFilterAcceptanceTest extends ParameterizedEnclaveTestBase { private final PrivacyNode node; diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/FlexibleMultiTenancyAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/FlexibleMultiTenancyAcceptanceTest.java index 7907c37a110..4378ab6de11 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/FlexibleMultiTenancyAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/FlexibleMultiTenancyAcceptanceTest.java @@ -40,6 +40,7 @@ import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -50,6 +51,7 @@ import org.web3j.utils.Restriction; @RunWith(Parameterized.class) +@Ignore("Ignored since Tessera/Docker container startup causing errors") public class FlexibleMultiTenancyAcceptanceTest extends FlexiblePrivacyAcceptanceTestBase { private final EnclaveType enclaveType; 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 d1ec8d28b15..78b86fb1791 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -20,6 +20,7 @@ import static java.util.Collections.singletonList; import static org.hyperledger.besu.cli.DefaultCommandValues.getDefaultBesuDataPath; import static org.hyperledger.besu.cli.config.NetworkName.MAINNET; +import static org.hyperledger.besu.cli.config.NetworkName.isMergedNetwork; import static org.hyperledger.besu.cli.util.CommandLineUtils.DEPENDENCY_WARNING_MSG; import static org.hyperledger.besu.cli.util.CommandLineUtils.DEPRECATED_AND_USELESS_WARNING_MSG; import static org.hyperledger.besu.cli.util.CommandLineUtils.DEPRECATION_WARNING_MSG; @@ -185,6 +186,7 @@ import org.hyperledger.besu.util.number.Fraction; import org.hyperledger.besu.util.number.Percentage; import org.hyperledger.besu.util.number.PositiveNumber; +import org.hyperledger.besu.util.platform.PlatformDetector; import java.io.File; import java.io.IOException; @@ -505,8 +507,12 @@ private InetAddress autoDiscoverDefaultIP() { names = {"--fast-sync-min-peers"}, paramLabel = MANDATORY_INTEGER_FORMAT_HELP, description = - "Minimum number of peers required before starting fast sync. (default: ${DEFAULT-VALUE})") - private final Integer fastSyncMinPeerCount = FAST_SYNC_MIN_PEER_COUNT; + "Minimum number of peers required before starting fast sync. (default pre-merge: " + + FAST_SYNC_MIN_PEER_COUNT + + " and post-merge: " + + FAST_SYNC_MIN_PEER_COUNT_POST_MERGE + + ")") + private final Integer fastSyncMinPeerCount = null; @Option( names = {"--network"}, @@ -1211,6 +1217,16 @@ static class TxPoolOptionGroup { "Price bump percentage to replace an already existing transaction (default: ${DEFAULT-VALUE})", arity = "1") private final Integer priceBump = TransactionPoolConfiguration.DEFAULT_PRICE_BUMP.getValue(); + + @Option( + names = {"--tx-pool-future-max-by-account"}, + paramLabel = MANDATORY_INTEGER_FORMAT_HELP, + converter = PercentageConverter.class, + description = + "Maximum per account of currently unexecutable future transactions that can occupy the txpool (default: ${DEFAULT-VALUE})", + arity = "1") + private final Integer maxFutureTransactionsByAccount = + TransactionPoolConfiguration.MAX_FUTURE_TRANSACTION_BY_ACCOUNT; } @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. @@ -1391,6 +1407,7 @@ public void parse( handleUnstableOptions(); preparePlugins(); parse(resultHandler, exceptionHandler, args); + detectJemalloc(); } @Override @@ -1492,6 +1509,18 @@ private void registerConverters() { commandLine.registerConverter(MetricCategory.class, metricCategoryConverter); } + private void detectJemalloc() { + // jemalloc is only supported on Linux at the moment + if (PlatformDetector.getOSType().equals("linux")) { + Optional.ofNullable(environment.get("BESU_USING_JEMALLOC")) + .ifPresentOrElse( + present -> logger.info("Using jemalloc"), + () -> + logger.info( + "jemalloc library not found, memory usage may be reduced by installing it")); + } + } + private void handleStableOptions() { commandLine.addMixin("Ethstats", ethstatsOptions); commandLine.addMixin("Private key file", nodePrivateKeyFileOption); @@ -2760,10 +2789,19 @@ private Optional maybePkiBlockCreationConfigurati } private SynchronizerConfiguration buildSyncConfig() { + Integer fastSyncMinPeers = fastSyncMinPeerCount; + if (fastSyncMinPeers == null) { + if (isMergedNetwork(network)) { + fastSyncMinPeers = FAST_SYNC_MIN_PEER_COUNT_POST_MERGE; + } else { + fastSyncMinPeers = FAST_SYNC_MIN_PEER_COUNT; + } + } + return unstableSynchronizerOptions .toDomainObject() .syncMode(syncMode) - .fastSyncMinimumPeerCount(fastSyncMinPeerCount) + .fastSyncMinimumPeerCount(fastSyncMinPeers) .build(); } @@ -2773,6 +2811,7 @@ private TransactionPoolConfiguration buildTransactionPoolConfiguration() { .txPoolMaxSize(txPoolOptionGroup.txPoolMaxSize) .pendingTxRetentionPeriod(txPoolOptionGroup.pendingTxRetentionPeriod) .priceBump(Percentage.fromInt(txPoolOptionGroup.priceBump)) + .txPoolMaxFutureTransactionByAccount(txPoolOptionGroup.maxFutureTransactionsByAccount) .txFeeCap(txFeeCap) .build(); } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/DefaultCommandValues.java b/besu/src/main/java/org/hyperledger/besu/cli/DefaultCommandValues.java index 2ca3b5d7ab1..ef4fe428525 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/DefaultCommandValues.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/DefaultCommandValues.java @@ -57,6 +57,7 @@ public interface DefaultCommandValues { NatMethod DEFAULT_NAT_METHOD = NatMethod.AUTO; JwtAlgorithm DEFAULT_JWT_ALGORITHM = JwtAlgorithm.RS256; int FAST_SYNC_MIN_PEER_COUNT = 5; + int FAST_SYNC_MIN_PEER_COUNT_POST_MERGE = 1; int DEFAULT_MAX_PEERS = 25; int DEFAULT_P2P_PEER_LOWER_BOUND = 25; int DEFAULT_HTTP_MAX_CONNECTIONS = 80; diff --git a/besu/src/main/java/org/hyperledger/besu/cli/config/NetworkName.java b/besu/src/main/java/org/hyperledger/besu/cli/config/NetworkName.java index 0592fcffc51..5505a0853fe 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/config/NetworkName.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/config/NetworkName.java @@ -86,4 +86,16 @@ public boolean isDeprecated() { public Optional getDeprecationDate() { return Optional.ofNullable(deprecationDate); } + + public static boolean isMergedNetwork(final NetworkName networkName) { + switch (networkName) { + case GOERLI: + case ROPSTEN: + case SEPOLIA: + case KILN: + return true; + default: + return false; + } + } } diff --git a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java index 71c912efc06..f804ad23deb 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -21,7 +21,6 @@ import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.consensus.merge.FinalizedBlockHashSupplier; import org.hyperledger.besu.consensus.merge.MergeContext; -import org.hyperledger.besu.consensus.merge.PandaPrinter; import org.hyperledger.besu.consensus.qbft.pki.PkiBlockCreationConfiguration; import org.hyperledger.besu.crypto.NodeKey; import org.hyperledger.besu.datatypes.Hash; @@ -422,8 +421,6 @@ public BesuController build() { ethProtocolManager, pivotBlockSelector); - synchronizer.subscribeInSync(new PandaPrinter()); - final MiningCoordinator miningCoordinator = createMiningCoordinator( protocolSchedule, @@ -467,7 +464,7 @@ public BesuController build() { additionalPluginServices); } - private Synchronizer createSynchronizer( + protected Synchronizer createSynchronizer( final ProtocolSchedule protocolSchedule, final WorldStateStorage worldStateStorage, final ProtocolContext protocolContext, @@ -477,8 +474,6 @@ private Synchronizer createSynchronizer( final EthProtocolManager ethProtocolManager, final PivotBlockSelector pivotBlockSelector) { - final GenesisConfigOptions maybeForTTD = configOptionsSupplier.get(); - DefaultSynchronizer toUse = new DefaultSynchronizer( syncConfig, @@ -494,13 +489,6 @@ private Synchronizer createSynchronizer( metricsSystem, getFullSyncTerminationCondition(protocolContext.getBlockchain()), pivotBlockSelector); - if (maybeForTTD.getTerminalTotalDifficulty().isPresent()) { - LOG.info( - "TTD present, creating DefaultSynchronizer that stops propagating after finalization"); - protocolContext - .getConsensusContext(MergeContext.class) - .addNewForkchoiceMessageListener(toUse); - } return toUse; } diff --git a/besu/src/main/java/org/hyperledger/besu/controller/TransitionBesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/TransitionBesuControllerBuilder.java index 4106949c091..b35f968e605 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/TransitionBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/TransitionBesuControllerBuilder.java @@ -15,6 +15,8 @@ package org.hyperledger.besu.controller; import org.hyperledger.besu.config.GenesisConfigFile; +import org.hyperledger.besu.config.GenesisConfigOptions; +import org.hyperledger.besu.consensus.merge.MergeContext; import org.hyperledger.besu.consensus.merge.PandaPrinter; import org.hyperledger.besu.consensus.merge.PostMergeContext; import org.hyperledger.besu.consensus.merge.TransitionBackwardSyncContext; @@ -29,8 +31,10 @@ import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator; import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.ethereum.core.Synchronizer; import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthMessages; @@ -39,6 +43,8 @@ import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.manager.MergePeerFilter; import org.hyperledger.besu.ethereum.eth.peervalidation.PeerValidator; +import org.hyperledger.besu.ethereum.eth.sync.DefaultSynchronizer; +import org.hyperledger.besu.ethereum.eth.sync.PivotBlockSelector; import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; import org.hyperledger.besu.ethereum.eth.sync.backwardsync.BackwardSyncContext; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; @@ -47,8 +53,10 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.ethereum.worldstate.Pruner; import org.hyperledger.besu.ethereum.worldstate.PrunerConfiguration; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.ObservableMetricsSystem; import org.hyperledger.besu.plugin.services.permissioning.NodeMessagePermissioningProvider; @@ -61,10 +69,15 @@ import java.util.Optional; import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class TransitionBesuControllerBuilder extends BesuControllerBuilder { private final BesuControllerBuilder preMergeBesuControllerBuilder; private final MergeBesuControllerBuilder mergeBesuControllerBuilder; + private static final Logger LOG = LoggerFactory.getLogger(TransitionBesuControllerBuilder.class); + public TransitionBesuControllerBuilder( final BesuControllerBuilder preMergeBesuControllerBuilder, final MergeBesuControllerBuilder mergeBesuControllerBuilder) { @@ -176,6 +189,50 @@ protected PluginServiceFactory createAdditionalPluginServices( return new NoopPluginServiceFactory(); } + @Override + protected Synchronizer createSynchronizer( + final ProtocolSchedule protocolSchedule, + final WorldStateStorage worldStateStorage, + final ProtocolContext protocolContext, + final Optional maybePruner, + final EthContext ethContext, + final SyncState syncState, + final EthProtocolManager ethProtocolManager, + final PivotBlockSelector pivotBlockSelector) { + + DefaultSynchronizer sync = + (DefaultSynchronizer) + super.createSynchronizer( + protocolSchedule, + worldStateStorage, + protocolContext, + maybePruner, + ethContext, + syncState, + ethProtocolManager, + pivotBlockSelector); + final GenesisConfigOptions maybeForTTD = configOptionsSupplier.get(); + + if (maybeForTTD.getTerminalTotalDifficulty().isPresent()) { + LOG.info( + "TTD present, creating DefaultSynchronizer that stops propagating after finalization"); + protocolContext.getConsensusContext(MergeContext.class).addNewForkchoiceMessageListener(sync); + Optional currentTotal = + protocolContext + .getBlockchain() + .getTotalDifficultyByHash(protocolContext.getBlockchain().getChainHeadHash()); + PandaPrinter.init( + currentTotal, Difficulty.of(maybeForTTD.getTerminalTotalDifficulty().get())); + sync.subscribeInSync(PandaPrinter.getInstance()); + protocolContext.getBlockchain().observeBlockAdded(PandaPrinter.getInstance()); + protocolContext + .getConsensusContext(MergeContext.class) + .addNewForkchoiceMessageListener(PandaPrinter.getInstance()); + } + + return sync; + } + private void initTransitionWatcher( final ProtocolContext protocolContext, final TransitionCoordinator composedCoordinator) { @@ -193,7 +250,7 @@ private void initTransitionWatcher( if (priorState.filter(prior -> !prior).isPresent()) { // only print pandas if we had a prior merge state, and it was false - PandaPrinter.printOnFirstCrossing(); + PandaPrinter.getInstance().printOnFirstCrossing(); } } else if (composedCoordinator.isMiningBeforeMerge()) { @@ -219,7 +276,6 @@ public BesuControllerBuilder storageProvider(final StorageProvider storageProvid @Override public BesuController build() { BesuController controller = super.build(); - PandaPrinter.hasTTD(); PostMergeContext.get().setSyncState(controller.getSyncState()); return controller; } diff --git a/besu/src/main/scripts/unixStartScript.txt b/besu/src/main/scripts/unixStartScript.txt index 50585f07660..3302b9c2fe1 100644 --- a/besu/src/main/scripts/unixStartScript.txt +++ b/besu/src/main/scripts/unixStartScript.txt @@ -182,8 +182,19 @@ APP_ARGS=`save "\$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- \$DEFAULT_JVM_OPTS \$JAVA_OPTS \$${optsEnvironmentVar} <% if ( appNameSystemProperty ) { %>"\"-D${appNameSystemProperty}=\$APP_BASE_NAME\"" <% } %>-classpath "\"\$CLASSPATH\"" <% if ( mainClassName.startsWith('--module ') ) { %>--module-path "\"\$MODULE_PATH\"" <% } %>${mainClassName} "\$APP_ARGS" -# limit malloc to 2 arenas, and use jemalloc if available -export MALLOC_ARENA_MAX=2 -export LD_PRELOAD=libjemalloc.so +unset BESU_USING_JEMALLOC +if [ "\$darwin" = "false" -a "\$msys" = "false" ]; then + # check if jemalloc is available + TEST_JEMALLOC=\$(LD_PRELOAD=libjemalloc.so sh -c true 2>&1) + + # if jemalloc is available the output is empty, otherwise the output has an error line + if [ -z "\$TEST_JEMALLOC" ]; then + export LD_PRELOAD=libjemalloc.so + export BESU_USING_JEMALLOC=true + else + # jemalloc not available, as fallback limit malloc to 2 arenas + export MALLOC_ARENA_MAX=2 + fi +fi exec "\$JAVACMD" "\$@" diff --git a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java index 1b64a1cdb65..d5eedd22b92 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java @@ -17,6 +17,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.startsWith; import static org.hyperledger.besu.cli.config.NetworkName.CLASSIC; import static org.hyperledger.besu.cli.config.NetworkName.DEV; @@ -94,6 +95,7 @@ import org.hyperledger.besu.plugin.services.rpc.PluginRpcRequest; import org.hyperledger.besu.util.number.Fraction; import org.hyperledger.besu.util.number.Percentage; +import org.hyperledger.besu.util.platform.PlatformDetector; import java.io.File; import java.io.IOException; @@ -1704,6 +1706,42 @@ public void helpShouldDisplayFastSyncOptions() { assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } + @Test + public void checkValidDefaultFastSyncMinPeersOption() { + parseCommand("--sync-mode", "FAST"); + verify(mockControllerBuilder).synchronizerConfiguration(syncConfigurationCaptor.capture()); + + final SynchronizerConfiguration syncConfig = syncConfigurationCaptor.getValue(); + assertThat(syncConfig.getSyncMode()).isEqualTo(SyncMode.FAST); + assertThat(syncConfig.getFastSyncMinimumPeerCount()).isEqualTo(5); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void checkValidDefaultFastSyncMinPeersPreMergeOption() { + parseCommand("--sync-mode", "FAST", "--network", "CLASSIC"); + verify(mockControllerBuilder).synchronizerConfiguration(syncConfigurationCaptor.capture()); + + final SynchronizerConfiguration syncConfig = syncConfigurationCaptor.getValue(); + assertThat(syncConfig.getSyncMode()).isEqualTo(SyncMode.FAST); + assertThat(syncConfig.getFastSyncMinimumPeerCount()).isEqualTo(5); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void checkValidDefaultFastSyncMinPeersPostMergeOption() { + parseCommand("--sync-mode", "FAST", "--network", "GOERLI"); + verify(mockControllerBuilder).synchronizerConfiguration(syncConfigurationCaptor.capture()); + + final SynchronizerConfiguration syncConfig = syncConfigurationCaptor.getValue(); + assertThat(syncConfig.getSyncMode()).isEqualTo(SyncMode.FAST); + assertThat(syncConfig.getFastSyncMinimumPeerCount()).isEqualTo(1); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + @Test public void parsesValidFastSyncMinPeersOption() { parseCommand("--sync-mode", "FAST", "--fast-sync-min-peers", "11"); @@ -1716,6 +1754,30 @@ public void parsesValidFastSyncMinPeersOption() { assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } + @Test + public void parsesValidFastSyncMinPeersOptionPreMerge() { + parseCommand("--sync-mode", "FAST", "--network", "CLASSIC", "--fast-sync-min-peers", "11"); + verify(mockControllerBuilder).synchronizerConfiguration(syncConfigurationCaptor.capture()); + + final SynchronizerConfiguration syncConfig = syncConfigurationCaptor.getValue(); + assertThat(syncConfig.getSyncMode()).isEqualTo(SyncMode.FAST); + assertThat(syncConfig.getFastSyncMinimumPeerCount()).isEqualTo(11); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void parsesValidFastSyncMinPeersOptionPostMerge() { + parseCommand("--sync-mode", "FAST", "--network", "GOERLI", "--fast-sync-min-peers", "11"); + verify(mockControllerBuilder).synchronizerConfiguration(syncConfigurationCaptor.capture()); + + final SynchronizerConfiguration syncConfig = syncConfigurationCaptor.getValue(); + assertThat(syncConfig.getSyncMode()).isEqualTo(SyncMode.FAST); + assertThat(syncConfig.getFastSyncMinimumPeerCount()).isEqualTo(11); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + @Test public void parsesInvalidFastSyncMinPeersOptionWrongFormatShouldFail() { @@ -5318,4 +5380,20 @@ public void pkiBlockCreationFullConfig() throws Exception { assertThat(pkiKeyStoreConfig.getTrustStorePassword()).isEqualTo("foo"); assertThat(pkiKeyStoreConfig.getCrlFilePath()).hasValue(Path.of("/tmp/crl")); } + + @Test + public void logsUsingJemallocWhenEnvVarPresent() { + assumeThat(PlatformDetector.getOSType(), is("linux")); + setEnvironmentVariable("BESU_USING_JEMALLOC", "true"); + parseCommand(); + verify(mockLogger).info("Using jemalloc"); + } + + @Test + public void logsSuggestInstallingJemallocWhenEnvVarNotPresent() { + assumeThat(PlatformDetector.getOSType(), is("linux")); + parseCommand(); + verify(mockLogger) + .info("jemalloc library not found, memory usage may be reduced by installing it"); + } } diff --git a/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java b/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java index 969c84ba20f..ab08f2010ed 100644 --- a/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java +++ b/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java @@ -66,6 +66,7 @@ import org.hyperledger.besu.testutil.TestClock; import java.math.BigInteger; +import java.time.ZoneId; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -144,7 +145,7 @@ public void setUp() { mockProtocolSchedule, mockProtocolContext, mockEthContext, - TestClock.fixed(), + TestClock.system(ZoneId.systemDefault()), new NoOpMetricsSystem(), syncState::isInitialSyncPhaseDone, new MiningParameters.Builder().minTransactionGasPrice(Wei.ZERO).build(), diff --git a/besu/src/test/resources/everything_config.toml b/besu/src/test/resources/everything_config.toml index 0855f14605c..458021ebed5 100644 --- a/besu/src/test/resources/everything_config.toml +++ b/besu/src/test/resources/everything_config.toml @@ -167,6 +167,7 @@ tx-pool-retention-hours=999 tx-pool-price-bump=13 tx-pool-max-size=1234 tx-pool-hashes-max-size=10000 +tx-pool-future-max-by-account=50 Xincoming-tx-messages-keep-alive-seconds=60 rpc-tx-feecap=2000000000000000000 strict-tx-replay-protection-enabled=true diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreatorTest.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreatorTest.java index d8572fe7add..e5fc13b7b1f 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreatorTest.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreatorTest.java @@ -47,7 +47,7 @@ import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.Util; -import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.sorter.GasPricePendingTransactionsSorter; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; @@ -56,6 +56,7 @@ import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.testutil.TestClock; +import java.time.ZoneId; import java.util.List; import java.util.Optional; @@ -131,12 +132,10 @@ public void proposerAddressCanBeExtractFromAConstructedBlock() { () -> Optional.of(10_000_000L), parent -> extraData, new GasPricePendingTransactionsSorter( - TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, - 5, - TestClock.fixed(), + ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(5).build(), + TestClock.system(ZoneId.systemDefault()), metricsSystem, - blockchain::getChainHeadHeader, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP), + blockchain::getChainHeadHeader), protocolContext, protocolSchedule, proposerNodeKey, @@ -166,12 +165,10 @@ public void insertsValidVoteIntoConstructedBlock() { () -> Optional.of(10_000_000L), parent -> extraData, new GasPricePendingTransactionsSorter( - TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, - 5, - TestClock.fixed(), + ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(5).build(), + TestClock.system(ZoneId.systemDefault()), metricsSystem, - blockchain::getChainHeadHeader, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP), + blockchain::getChainHeadHeader), protocolContext, protocolSchedule, proposerNodeKey, @@ -203,12 +200,10 @@ public void insertsNoVoteWhenAtEpoch() { () -> Optional.of(10_000_000L), parent -> extraData, new GasPricePendingTransactionsSorter( - TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, - 5, - TestClock.fixed(), + ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(5).build(), + TestClock.system(ZoneId.systemDefault()), metricsSystem, - blockchain::getChainHeadHeader, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP), + blockchain::getChainHeadHeader), protocolContext, protocolSchedule, proposerNodeKey, diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutorTest.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutorTest.java index c76a27d30b5..b5d69c0f494 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutorTest.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutorTest.java @@ -38,13 +38,14 @@ import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Util; -import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.sorter.GasPricePendingTransactionsSorter; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.testutil.TestClock; +import java.time.ZoneId; import java.util.List; import java.util.Optional; import java.util.Random; @@ -94,12 +95,10 @@ public void extraDataCreatedOnEpochBlocksContainsValidators() { CliqueProtocolSchedule.create( GENESIS_CONFIG_OPTIONS, proposerNodeKey, false, EvmConfiguration.DEFAULT), new GasPricePendingTransactionsSorter( - TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, - 1, - TestClock.fixed(), + ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(1).build(), + TestClock.system(ZoneId.systemDefault()), metricsSystem, - CliqueMinerExecutorTest::mockBlockHeader, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP), + CliqueMinerExecutorTest::mockBlockHeader), proposerNodeKey, new MiningParameters.Builder() .coinbase(AddressHelpers.ofValue(1)) @@ -138,12 +137,10 @@ public void extraDataForNonEpochBlocksDoesNotContainValidaors() { CliqueProtocolSchedule.create( GENESIS_CONFIG_OPTIONS, proposerNodeKey, false, EvmConfiguration.DEFAULT), new GasPricePendingTransactionsSorter( - TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, - 1, - TestClock.fixed(), + ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(1).build(), + TestClock.system(ZoneId.systemDefault()), metricsSystem, - CliqueMinerExecutorTest::mockBlockHeader, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP), + CliqueMinerExecutorTest::mockBlockHeader), proposerNodeKey, new MiningParameters.Builder() .coinbase(AddressHelpers.ofValue(1)) @@ -182,12 +179,10 @@ public void shouldUseLatestVanityData() { CliqueProtocolSchedule.create( GENESIS_CONFIG_OPTIONS, proposerNodeKey, false, EvmConfiguration.DEFAULT), new GasPricePendingTransactionsSorter( - TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, - 1, - TestClock.fixed(), + ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(1).build(), + TestClock.system(ZoneId.systemDefault()), metricsSystem, - CliqueMinerExecutorTest::mockBlockHeader, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP), + CliqueMinerExecutorTest::mockBlockHeader), proposerNodeKey, new MiningParameters.Builder() .coinbase(AddressHelpers.ofValue(1)) diff --git a/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContextBuilder.java b/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContextBuilder.java index c905641bd17..fbe42840b07 100644 --- a/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContextBuilder.java +++ b/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContextBuilder.java @@ -75,7 +75,7 @@ import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Util; -import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.sorter.GasPricePendingTransactionsSorter; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; @@ -332,12 +332,10 @@ private static ControllerAndState createControllerAndFinalState( final GasPricePendingTransactionsSorter pendingTransactions = new GasPricePendingTransactionsSorter( - TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, - 1, + ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(1).build(), clock, metricsSystem, - blockChain::getChainHeadHeader, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP); + blockChain::getChainHeadHeader); final Address localAddress = Util.publicKeyToAddress(nodeKey.getPublicKey()); final BftBlockCreatorFactory blockCreatorFactory = diff --git a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/BftBlockCreatorTest.java b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/BftBlockCreatorTest.java index 764d6040205..15d83d291a8 100644 --- a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/BftBlockCreatorTest.java +++ b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/BftBlockCreatorTest.java @@ -41,7 +41,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.PrivacyParameters; -import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.sorter.GasPricePendingTransactionsSorter; import org.hyperledger.besu.ethereum.mainnet.BlockHeaderValidator; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; @@ -52,6 +52,7 @@ import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.testutil.TestClock; +import java.time.ZoneId; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -116,12 +117,10 @@ public BlockHeaderValidator.Builder createBlockHeaderRuleset( final GasPricePendingTransactionsSorter pendingTransactions = new GasPricePendingTransactionsSorter( - TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, - 1, - TestClock.fixed(), + ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(1).build(), + TestClock.system(ZoneId.systemDefault()), metricsSystem, - blockchain::getChainHeadHeader, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP); + blockchain::getChainHeadHeader); final BftBlockCreator blockCreator = new BftBlockCreator( diff --git a/consensus/ibftlegacy/src/test/java/org/hyperledger/besu/consensus/ibftlegacy/blockcreation/BftBlockCreatorTest.java b/consensus/ibftlegacy/src/test/java/org/hyperledger/besu/consensus/ibftlegacy/blockcreation/BftBlockCreatorTest.java index f051f8f99b4..709c37ae087 100644 --- a/consensus/ibftlegacy/src/test/java/org/hyperledger/besu/consensus/ibftlegacy/blockcreation/BftBlockCreatorTest.java +++ b/consensus/ibftlegacy/src/test/java/org/hyperledger/besu/consensus/ibftlegacy/blockcreation/BftBlockCreatorTest.java @@ -35,7 +35,7 @@ import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; -import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.sorter.GasPricePendingTransactionsSorter; import org.hyperledger.besu.ethereum.mainnet.BlockHeaderValidator; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; @@ -46,6 +46,7 @@ import org.hyperledger.besu.testutil.TestClock; import java.time.Instant; +import java.time.ZoneId; import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -105,12 +106,10 @@ public void headerProducedPassesValidationRules() { Bytes.wrap(new byte[32]), Lists.newArrayList(), null, initialValidatorList) .encode(), new GasPricePendingTransactionsSorter( - TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, - 1, - TestClock.fixed(), + ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(1).build(), + TestClock.system(ZoneId.systemDefault()), metricsSystem, - blockchain::getChainHeadHeader, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP), + blockchain::getChainHeadHeader), protContext, protocolSchedule, nodeKeys, diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/PandaPrinter.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/PandaPrinter.java index b5340c70b5a..66ff5b1cf91 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/PandaPrinter.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/PandaPrinter.java @@ -17,6 +17,9 @@ package org.hyperledger.besu.consensus.merge; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.chain.BlockAddedEvent; +import org.hyperledger.besu.ethereum.chain.BlockAddedObserver; +import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.Synchronizer.InSyncListener; @@ -31,18 +34,49 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class PandaPrinter implements InSyncListener, ForkchoiceMessageListener, MergeStateHandler { +public class PandaPrinter implements InSyncListener, ForkchoiceMessageListener, BlockAddedObserver { + + private static PandaPrinter INSTANCE; + + public static PandaPrinter init(final Optional currentTotal, final Difficulty ttd) { + if (INSTANCE != null) { + LOG.debug("overwriting already initialized panda printer"); + } + + INSTANCE = new PandaPrinter(currentTotal, ttd); + + return INSTANCE; + } + + public static PandaPrinter getInstance() { + if (INSTANCE == null) { + throw new IllegalStateException("Uninitialized, unknown ttd"); + } + return INSTANCE; + } + + protected PandaPrinter(final Optional currentTotal, final Difficulty ttd) { + this.ttd = ttd; + if (currentTotal.isPresent() && currentTotal.get().greaterOrEqualThan(ttd)) { + this.readyBeenDisplayed.set(true); + this.ttdBeenDisplayed.set(true); + this.finalizedBeenDisplayed.set(true); + } + } private static final Logger LOG = LoggerFactory.getLogger(PandaPrinter.class); private static final String readyBanner = PandaPrinter.loadBanner("/readyPanda.txt"); private static final String ttdBanner = PandaPrinter.loadBanner("/ttdPanda.txt"); private static final String finalizedBanner = PandaPrinter.loadBanner("/finalizedPanda.txt"); - private static final AtomicBoolean hasTTD = new AtomicBoolean(false); - private static final AtomicBoolean inSync = new AtomicBoolean(false); - public static final AtomicBoolean readyBeenDisplayed = new AtomicBoolean(); - public static final AtomicBoolean ttdBeenDisplayed = new AtomicBoolean(); - public static final AtomicBoolean finalizedBeenDisplayed = new AtomicBoolean(); + private final Difficulty ttd; + private final AtomicBoolean hasTTD = new AtomicBoolean(false); + private final AtomicBoolean inSync = new AtomicBoolean(false); + private final AtomicBoolean isPoS = new AtomicBoolean(false); + + public final AtomicBoolean readyBeenDisplayed = new AtomicBoolean(); + public final AtomicBoolean ttdBeenDisplayed = new AtomicBoolean(); + public final AtomicBoolean finalizedBeenDisplayed = new AtomicBoolean(); private static String loadBanner(final String filename) { Class c = PandaPrinter.class; @@ -55,55 +89,56 @@ private static String loadBanner(final String filename) { resultStringBuilder.append(line).append("\n"); } } catch (IOException e) { - LOG.error("Couldn't load hilarious panda banner"); + LOG.error("Couldn't load hilarious panda banner at {} ", filename); } return resultStringBuilder.toString(); } - public static void hasTTD() { - PandaPrinter.hasTTD.getAndSet(true); - if (hasTTD.get() && inSync.get()) { - printReadyToMerge(); - } + public void hasTTD() { + this.hasTTD.getAndSet(true); } - public static void inSync() { - PandaPrinter.inSync.getAndSet(true); - if (inSync.get() && hasTTD.get()) { - printReadyToMerge(); - } + public void inSync() { + this.inSync.getAndSet(true); } - public static void printOnFirstCrossing() { + public void printOnFirstCrossing() { if (!ttdBeenDisplayed.get()) { + LOG.info("Crossed TTD, merging underway!"); LOG.info("\n" + ttdBanner); + ttdBeenDisplayed.compareAndSet(false, true); } - ttdBeenDisplayed.compareAndSet(false, true); } - static void resetForTesting() { + public void resetForTesting() { ttdBeenDisplayed.set(false); readyBeenDisplayed.set(false); finalizedBeenDisplayed.set(false); + hasTTD.set(false); + isPoS.set(false); + inSync.set(false); } - public static void printReadyToMerge() { - if (!readyBeenDisplayed.get()) { + public void printReadyToMerge() { + if (!readyBeenDisplayed.get() && !isPoS.get() && inSync.get()) { + LOG.info("Configured for TTD and in sync, still receiving PoW blocks. Ready to merge!"); LOG.info("\n" + readyBanner); + readyBeenDisplayed.compareAndSet(false, true); } - readyBeenDisplayed.compareAndSet(false, true); } - public static void printFinalized() { + public void printFinalized() { if (!finalizedBeenDisplayed.get()) { + LOG.info("Beacon chain finalized, welcome to Proof of Stake Ethereum"); LOG.info("\n" + finalizedBanner); + finalizedBeenDisplayed.compareAndSet(false, true); } - finalizedBeenDisplayed.compareAndSet(false, true); } @Override public void onInSyncStatusChange(final boolean newSyncStatus) { - if (newSyncStatus && hasTTD.get()) { + inSync.set(newSyncStatus); + if (newSyncStatus && hasTTD.get() && !isPoS.get()) { printReadyToMerge(); } } @@ -119,12 +154,22 @@ public void onNewForkchoiceMessage( } @Override - public void mergeStateChanged( - final boolean isPoS, - final Optional priorState, - final Optional difficultyStoppedAt) { - if (isPoS && priorState.isPresent() && !priorState.get()) { // just crossed from PoW to PoS - printOnFirstCrossing(); + public void onBlockAdded(final BlockAddedEvent event) { + if (event.isNewCanonicalHead()) { + Block added = event.getBlock(); + if (added.getHeader().getDifficulty().greaterOrEqualThan(this.ttd)) { + this.isPoS.set(true); + if (this.inSync.get()) { + this.printOnFirstCrossing(); + } + } else { + this.isPoS.set(false); + if (this.inSync.get()) { + if (!readyBeenDisplayed.get()) { + this.printReadyToMerge(); + } + } + } } } } diff --git a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/PandaPrinterTest.java b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/PandaPrinterTest.java index d81aa50e6e3..d596ba1b85a 100644 --- a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/PandaPrinterTest.java +++ b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/PandaPrinterTest.java @@ -17,85 +17,121 @@ package org.hyperledger.besu.consensus.merge; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.chain.BlockAddedEvent; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockBody; +import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Difficulty; +import java.math.BigInteger; +import java.util.ArrayList; import java.util.Optional; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; +@RunWith(MockitoJUnitRunner.class) public class PandaPrinterTest { final MergeStateHandler fauxTransitionHandler = (isPoS, priorState, ttd) -> { if (isPoS && priorState.filter(prior -> !prior).isPresent()) - PandaPrinter.printOnFirstCrossing(); + PandaPrinter.getInstance().printOnFirstCrossing(); }; @Test public void printsPanda() { - PandaPrinter.resetForTesting(); - assertThat(PandaPrinter.ttdBeenDisplayed).isFalse(); - PandaPrinter.printOnFirstCrossing(); - assertThat(PandaPrinter.ttdBeenDisplayed).isTrue(); - assertThat(PandaPrinter.readyBeenDisplayed).isFalse(); - assertThat(PandaPrinter.finalizedBeenDisplayed).isFalse(); + PandaPrinter p = new PandaPrinter(Optional.of(Difficulty.of(1)), Difficulty.of(BigInteger.TEN)); + p.resetForTesting(); + assertThat(p.ttdBeenDisplayed).isFalse(); + p.printOnFirstCrossing(); + assertThat(p.ttdBeenDisplayed).isTrue(); + assertThat(p.readyBeenDisplayed).isFalse(); + assertThat(p.finalizedBeenDisplayed).isFalse(); } @Test - public void doesNotPrintAtInit() { - PandaPrinter.resetForTesting(); - var mergeContext = new PostMergeContext(Difficulty.ONE); + public void doesNotPrintAtPreMergeInit() { + PandaPrinter p = new PandaPrinter(Optional.of(Difficulty.of(1)), Difficulty.of(BigInteger.TEN)); + + var mergeContext = new PostMergeContext(Difficulty.of(BigInteger.TEN)); mergeContext.observeNewIsPostMergeState(fauxTransitionHandler); - assertThat(PandaPrinter.ttdBeenDisplayed).isFalse(); + assertThat(p.ttdBeenDisplayed).isFalse(); mergeContext.setIsPostMerge(Difficulty.ONE); - assertThat(PandaPrinter.ttdBeenDisplayed).isFalse(); - assertThat(PandaPrinter.readyBeenDisplayed).isFalse(); - assertThat(PandaPrinter.finalizedBeenDisplayed).isFalse(); + assertThat(p.ttdBeenDisplayed).isFalse(); + assertThat(p.readyBeenDisplayed).isFalse(); + assertThat(p.finalizedBeenDisplayed).isFalse(); } @Test public void printsWhenCrossingOnly() { - PandaPrinter.resetForTesting(); - var mergeContext = new PostMergeContext(Difficulty.ONE); - mergeContext.observeNewIsPostMergeState(fauxTransitionHandler); + PandaPrinter p = new PandaPrinter(Optional.of(Difficulty.of(1)), Difficulty.of(10)); + p.inSync(); + p.hasTTD(); + assertThat(p.ttdBeenDisplayed).isFalse(); + p.onBlockAdded(withDifficulty(Difficulty.of(11))); + assertThat(p.ttdBeenDisplayed).isTrue(); + assertThat(p.readyBeenDisplayed).isFalse(); + assertThat(p.finalizedBeenDisplayed).isFalse(); + } - assertThat(PandaPrinter.ttdBeenDisplayed).isFalse(); - mergeContext.setIsPostMerge(Difficulty.ZERO); - assertThat(PandaPrinter.ttdBeenDisplayed).isFalse(); - mergeContext.setIsPostMerge(Difficulty.ONE); - assertThat(PandaPrinter.ttdBeenDisplayed).isTrue(); - assertThat(PandaPrinter.readyBeenDisplayed).isFalse(); - assertThat(PandaPrinter.finalizedBeenDisplayed).isFalse(); + @Test + public void printsReadyOnStartupInSyncWithPoWTTD() { + PandaPrinter p = new PandaPrinter(Optional.of(Difficulty.of(1)), Difficulty.of(10)); + p.inSync(); + p.hasTTD(); + p.onBlockAdded(withDifficulty(Difficulty.of(2))); + assertThat(p.readyBeenDisplayed).isTrue(); + assertThat(p.ttdBeenDisplayed).isFalse(); + assertThat(p.finalizedBeenDisplayed).isFalse(); } @Test - public void printsReadyOnStartupInSyncWithTTD() { - PandaPrinter.resetForTesting(); - PandaPrinter.inSync(); - PandaPrinter.hasTTD(); - assertThat(PandaPrinter.readyBeenDisplayed).isTrue(); - assertThat(PandaPrinter.ttdBeenDisplayed).isFalse(); - assertThat(PandaPrinter.finalizedBeenDisplayed).isFalse(); + public void noPandasPostTTD() { + PandaPrinter p = + new PandaPrinter(Optional.of(Difficulty.of(11)), Difficulty.of(BigInteger.TEN)); + p.inSync(); + p.hasTTD(); + + assertThat(p.readyBeenDisplayed).isTrue(); + assertThat(p.ttdBeenDisplayed).isTrue(); + assertThat(p.finalizedBeenDisplayed).isTrue(); + p.onBlockAdded(withDifficulty(Difficulty.of(11))); + assertThat(p.readyBeenDisplayed).isTrue(); + assertThat(p.ttdBeenDisplayed).isTrue(); + assertThat(p.finalizedBeenDisplayed).isTrue(); } @Test public void printsFinalized() { - PandaPrinter.resetForTesting(); - PandaPrinter pandaPrinter = new PandaPrinter(); + PandaPrinter p = new PandaPrinter(Optional.of(Difficulty.of(9)), Difficulty.of(BigInteger.TEN)); + assertThat(p.finalizedBeenDisplayed).isFalse(); MergeContext mergeContext = new PostMergeContext(Difficulty.ZERO); - mergeContext.addNewForkchoiceMessageListener(pandaPrinter); + mergeContext.addNewForkchoiceMessageListener(p); mergeContext.fireNewUnverifiedForkchoiceMessageEvent( Hash.ZERO, Optional.of(Hash.ZERO), Hash.ZERO); - assertThat(PandaPrinter.readyBeenDisplayed).isFalse(); - assertThat(PandaPrinter.finalizedBeenDisplayed).isFalse(); - assertThat(PandaPrinter.ttdBeenDisplayed).isFalse(); mergeContext.fireNewUnverifiedForkchoiceMessageEvent( Hash.ZERO, Optional.of(Hash.fromHexStringLenient("0x1337")), Hash.ZERO); - assertThat(PandaPrinter.readyBeenDisplayed).isFalse(); - assertThat(PandaPrinter.ttdBeenDisplayed).isFalse(); - assertThat(PandaPrinter.finalizedBeenDisplayed).isTrue(); + assertThat(p.finalizedBeenDisplayed).isTrue(); + } + + private BlockAddedEvent withDifficulty(final Difficulty diff) { + BlockBody mockBody = mock(BlockBody.class); + when(mockBody.getTransactions()).thenReturn(new ArrayList<>()); + BlockHeader mockHeader = mock(BlockHeader.class); + when(mockHeader.getDifficulty()).thenReturn(diff); + when(mockHeader.getParentHash()).thenReturn(Hash.ZERO); + Block mockBlock = mock(Block.class); + when(mockBlock.getHeader()).thenReturn(mockHeader); + when(mockBlock.getBody()).thenReturn(mockBody); + BlockAddedEvent powArrived = + BlockAddedEvent.createForHeadAdvancement(mockBlock, new ArrayList<>(), new ArrayList<>()); + return powArrived; } } diff --git a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java index 6ddd1450ac5..d12be771a75 100644 --- a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java +++ b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java @@ -89,7 +89,7 @@ import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.ProtocolScheduleFixture; import org.hyperledger.besu.ethereum.core.Util; -import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.sorter.GasPricePendingTransactionsSorter; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; @@ -437,12 +437,10 @@ private static ControllerAndState createControllerAndFinalState( final GasPricePendingTransactionsSorter pendingTransactions = new GasPricePendingTransactionsSorter( - TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, - 1, + ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(1).build(), clock, metricsSystem, - blockChain::getChainHeadHeader, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP); + blockChain::getChainHeadHeader); final Address localAddress = Util.publicKeyToAddress(nodeKey.getPublicKey()); final BftBlockCreatorFactory blockCreatorFactory = diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java index 5e23ff8d21a..aa19235fdee 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java @@ -17,6 +17,7 @@ import org.hyperledger.besu.plugin.data.Quantity; import java.math.BigInteger; +import java.util.Arrays; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.BaseUInt256Value; @@ -98,4 +99,52 @@ public String toShortHexString() { public static Wei fromQuantity(final Quantity quantity) { return Wei.wrap((Bytes) quantity); } + + public String toHumanReadableString() { + final BigInteger amount = toBigInteger(); + final int numOfDigits = amount.toString().length(); + final Unit preferredUnit = Unit.getPreferred(numOfDigits); + final double res = amount.doubleValue() / preferredUnit.divisor; + return String.format("%1." + preferredUnit.decimals + "f %s", res, preferredUnit); + } + + enum Unit { + Wei(0, 0), + KWei(3), + MWei(6), + GWei(9), + Szabo(12), + Finney(15), + Ether(18), + KEther(21), + MEther(24), + GEther(27), + TEther(30); + + final int pow; + final double divisor; + final int decimals; + + Unit(final int pow) { + this(pow, 2); + } + + Unit(final int pow, final int decimals) { + this.pow = pow; + this.decimals = decimals; + this.divisor = Math.pow(10, pow); + } + + static Unit getPreferred(final int numOfDigits) { + return Arrays.stream(values()) + .filter(u -> numOfDigits <= u.pow + 3) + .findFirst() + .orElse(TEther); + } + + @Override + public String toString() { + return name().toLowerCase(); + } + } } diff --git a/datatypes/src/test/java/org/hyperledger/besu/datatypes/WeiTest.java b/datatypes/src/test/java/org/hyperledger/besu/datatypes/WeiTest.java new file mode 100644 index 00000000000..aa9a82b9110 --- /dev/null +++ b/datatypes/src/test/java/org/hyperledger/besu/datatypes/WeiTest.java @@ -0,0 +1,47 @@ +/* + * Copyright Hyperledger Besu Contributors. + * + * 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.datatypes; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigInteger; +import java.util.Arrays; + +import org.junit.Test; + +public class WeiTest { + + @Test + public void toHumanReadableString() { + assertThat(Wei.ZERO.toHumanReadableString()).isEqualTo("0 wei"); + assertThat(Wei.ONE.toHumanReadableString()).isEqualTo("1 wei"); + + assertThat(Wei.of(999).toHumanReadableString()).isEqualTo("999 wei"); + assertThat(Wei.of(1000).toHumanReadableString()).isEqualTo("1.00 kwei"); + + assertThat(Wei.of(1009).toHumanReadableString()).isEqualTo("1.01 kwei"); + assertThat(Wei.of(1011).toHumanReadableString()).isEqualTo("1.01 kwei"); + + assertThat(Wei.of(new BigInteger("1000000000")).toHumanReadableString()).isEqualTo("1.00 gwei"); + + assertThat(Wei.of(new BigInteger("1000000000000000000")).toHumanReadableString()) + .isEqualTo("1.00 ether"); + + final char[] manyZeros = new char[32]; + Arrays.fill(manyZeros, '0'); + assertThat(Wei.of(new BigInteger("1" + String.valueOf(manyZeros))).toHumanReadableString()) + .isEqualTo("100.00 tether"); + } +} diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthGetFilterChangesIntegrationTest.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthGetFilterChangesIntegrationTest.java index d2bc8b7f5ca..f6b1d21e157 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthGetFilterChangesIntegrationTest.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthGetFilterChangesIntegrationTest.java @@ -48,6 +48,7 @@ import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthPeers; +import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; @@ -97,12 +98,10 @@ public void setUp() { blockchain = executionContext.getBlockchain(); transactions = new GasPricePendingTransactionsSorter( - TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, - MAX_TRANSACTIONS, + ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(MAX_TRANSACTIONS).build(), TestClock.fixed(), metricsSystem, - blockchain::getChainHeadHeader, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP); + blockchain::getChainHeadHeader); final ProtocolContext protocolContext = executionContext.getProtocolContext(); EthContext ethContext = mock(EthContext.class); diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthGetFilterChangesIntegrationTest.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthGetFilterChangesIntegrationTest.java index c7dfd71beab..3863e406070 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthGetFilterChangesIntegrationTest.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthGetFilterChangesIntegrationTest.java @@ -48,6 +48,7 @@ import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthPeers; +import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; @@ -97,12 +98,10 @@ public void setUp() { blockchain = executionContext.getBlockchain(); transactions = new BaseFeePendingTransactionsSorter( - TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, - MAX_TRANSACTIONS, + ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(MAX_TRANSACTIONS).build(), TestClock.fixed(), metricsSystem, - blockchain::getChainHeadHeader, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP); + blockchain::getChainHeadHeader); final ProtocolContext protocolContext = executionContext.getProtocolContext(); EthContext ethContext = mock(EthContext.class); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpService.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpService.java index 02333398fd9..874919bac52 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpService.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpService.java @@ -28,6 +28,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.execution.TimedJsonRpcProcessor; import org.hyperledger.besu.ethereum.api.jsonrpc.execution.TracedJsonRpcProcessor; import org.hyperledger.besu.ethereum.api.jsonrpc.health.HealthService; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.Logging403ErrorHandler; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; import org.hyperledger.besu.ethereum.api.tls.TlsClientAuthConfiguration; import org.hyperledger.besu.ethereum.api.tls.TlsConfiguration; @@ -298,7 +299,7 @@ private Router buildRouter() { // Verify Host header to avoid rebind attack. router.route().handler(checkAllowlistHostHeader()); - + router.errorHandler(403, new Logging403ErrorHandler()); router .route() .handler( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcService.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcService.java index 883528c2a6b..a3751feb620 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcService.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcService.java @@ -30,6 +30,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.execution.TimedJsonRpcProcessor; import org.hyperledger.besu.ethereum.api.jsonrpc.execution.TracedJsonRpcProcessor; import org.hyperledger.besu.ethereum.api.jsonrpc.health.HealthService; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.Logging403ErrorHandler; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketMessageHandler; @@ -402,7 +403,7 @@ private Router buildRouter() { // Verify Host header to avoid rebind attack. router.route().handler(denyRouteToBlockedHost()); - + router.errorHandler(403, new Logging403ErrorHandler()); router .route() .handler( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/exception/Logging403ErrorHandler.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/exception/Logging403ErrorHandler.java new file mode 100644 index 00000000000..dcd6d8873df --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/exception/Logging403ErrorHandler.java @@ -0,0 +1,38 @@ +/* + * Copyright Hyperledger Besu Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception; + +import io.vertx.core.Handler; +import io.vertx.core.http.HttpServerResponse; +import io.vertx.ext.web.RoutingContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Logging403ErrorHandler implements Handler { + + private static final Logger LOG = LoggerFactory.getLogger(Logging403ErrorHandler.class); + + @Override + public void handle(final RoutingContext event) { + LOG.error(event.failure().getMessage()); + LOG.debug(event.failure().getMessage(), event.failure()); + int statusCode = event.statusCode(); + + HttpServerResponse response = event.response(); + response.setStatusCode(statusCode).end("Exception thrown handling RPC"); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java index 73c94f47796..eb97fe4494c 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java @@ -25,12 +25,15 @@ import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.BlockValidator; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EnginePayloadParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EnginePayloadStatusResult; @@ -41,9 +44,11 @@ import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.encoding.TransactionDecoder; +import org.hyperledger.besu.ethereum.eth.manager.EthPeers; import org.hyperledger.besu.ethereum.mainnet.BodyValidation; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; import org.hyperledger.besu.ethereum.rlp.RLPException; +import org.hyperledger.besu.plugin.services.exception.StorageException; import java.util.Collections; import java.util.List; @@ -62,13 +67,16 @@ public class EngineNewPayload extends ExecutionEngineJsonRpcMethod { private static final Logger LOG = LoggerFactory.getLogger(EngineNewPayload.class); private static final BlockHeaderFunctions headerFunctions = new MainnetBlockHeaderFunctions(); private final MergeMiningCoordinator mergeCoordinator; + private final EthPeers ethPeers; public EngineNewPayload( final Vertx vertx, final ProtocolContext protocolContext, - final MergeMiningCoordinator mergeCoordinator) { + final MergeMiningCoordinator mergeCoordinator, + final EthPeers ethPeers) { super(vertx, protocolContext); this.mergeCoordinator = mergeCoordinator; + this.ethPeers = ethPeers; } @Override @@ -200,11 +208,22 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) } // execute block and return result response + final long startTimeMs = System.currentTimeMillis(); final BlockValidator.Result executionResult = mergeCoordinator.rememberBlock(block); if (executionResult.errorMessage.isEmpty()) { + logImportedBlockInfo(block, (System.currentTimeMillis() - startTimeMs) / 1000.0); return respondWith(reqId, blockParam, newBlockHeader.getHash(), VALID); } else { + if (executionResult.cause.isPresent()) { + // TODO; would prefer to invert the logic so we rpc error on anything that isn't a + // consensus error + if (executionResult.cause.get() instanceof StorageException) { + JsonRpcError error = JsonRpcError.INTERNAL_ERROR; + JsonRpcErrorResponse response = new JsonRpcErrorResponse(reqId, error); + return response; + } + } LOG.debug("New payload is invalid: {}", executionResult.errorMessage.get()); return respondWithInvalid( reqId, blockParam, latestValidAncestor.get(), executionResult.errorMessage.get()); @@ -252,4 +271,18 @@ JsonRpcResponse respondWithInvalid( requestId, new EnginePayloadStatusResult(INVALID, latestValidHash, Optional.of(validationError))); } + + private void logImportedBlockInfo(final Block block, final double timeInS) { + LOG.info( + String.format( + "Imported #%,d / %d tx / base fee %s / %,d (%01.1f%%) gas / (%s) in %01.3fs. Peers: %d", + block.getHeader().getNumber(), + block.getBody().getTransactions().size(), + block.getHeader().getBaseFee().map(Wei::toHumanReadableString).orElse("N/A"), + block.getHeader().getGasUsed(), + (block.getHeader().getGasUsed() * 100.0) / block.getHeader().getGasLimit(), + block.getHash().toHexString(), + timeInS, + ethPeers.peerCount())); + } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ExecutionEngineJsonRpcMethods.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ExecutionEngineJsonRpcMethods.java index 8c81681974e..7336f461ac6 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ExecutionEngineJsonRpcMethods.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ExecutionEngineJsonRpcMethods.java @@ -24,6 +24,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineNewPayload; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory; import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator; +import org.hyperledger.besu.ethereum.eth.manager.EthPeers; import java.util.Map; import java.util.Optional; @@ -38,14 +39,19 @@ public class ExecutionEngineJsonRpcMethods extends ApiGroupJsonRpcMethods { private final Optional mergeCoordinator; private final ProtocolContext protocolContext; + private final EthPeers ethPeers; + ExecutionEngineJsonRpcMethods( - final MiningCoordinator miningCoordinator, final ProtocolContext protocolContext) { + final MiningCoordinator miningCoordinator, + final ProtocolContext protocolContext, + final EthPeers ethPeers) { this.mergeCoordinator = Optional.ofNullable(miningCoordinator) .filter(mc -> mc.isCompatibleWithEngineApi()) .map(MergeMiningCoordinator.class::cast); this.protocolContext = protocolContext; + this.ethPeers = ethPeers; } @Override @@ -59,7 +65,7 @@ protected Map create() { if (mergeCoordinator.isPresent()) { return mapOf( new EngineGetPayload(syncVertx, protocolContext, blockResultFactory), - new EngineNewPayload(syncVertx, protocolContext, mergeCoordinator.get()), + new EngineNewPayload(syncVertx, protocolContext, mergeCoordinator.get(), ethPeers), new EngineForkchoiceUpdated(syncVertx, protocolContext, mergeCoordinator.get()), new EngineExchangeTransitionConfiguration(syncVertx, protocolContext)); } else { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java index 59bf78ee886..bf63527949a 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java @@ -94,7 +94,7 @@ public Map methods( blockchainQueries, protocolSchedule, metricsSystem, transactionPool, dataDir), new EeaJsonRpcMethods( blockchainQueries, protocolSchedule, transactionPool, privacyParameters), - new ExecutionEngineJsonRpcMethods(miningCoordinator, protocolContext), + new ExecutionEngineJsonRpcMethods(miningCoordinator, protocolContext, ethPeers), new GoQuorumJsonRpcPrivacyMethods( blockchainQueries, protocolSchedule, transactionPool, privacyParameters), new EthJsonRpcMethods( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketService.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketService.java index cee396161ee..f6131339cc3 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketService.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketService.java @@ -19,6 +19,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.authentication.AuthenticationService; import org.hyperledger.besu.ethereum.api.jsonrpc.authentication.AuthenticationUtils; import org.hyperledger.besu.ethereum.api.jsonrpc.authentication.DefaultAuthenticationService; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.Logging403ErrorHandler; import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.SubscriptionManager; import org.hyperledger.besu.metrics.BesuMetricCategory; import org.hyperledger.besu.plugin.services.MetricsSystem; @@ -222,7 +223,7 @@ private Handler httpHandler() { .produces(APPLICATION_JSON) .handler(DefaultAuthenticationService::handleDisabledLogin); } - + router.errorHandler(403, new Logging403ErrorHandler()); router.route().handler(WebSocketService::handleHttpNotSupported); return router; } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadTest.java index 74d48c62d29..52ed5ee505c 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadTest.java @@ -39,6 +39,8 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EnginePayloadParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.UnsignedLongParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponseType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -48,6 +50,8 @@ import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.ethereum.eth.manager.EthPeers; +import org.hyperledger.besu.plugin.services.exception.StorageException; import java.util.Collections; import java.util.List; @@ -77,11 +81,14 @@ public class EngineNewPayloadTest { @Mock private MutableBlockchain blockchain; + @Mock private EthPeers ethPeers; + @Before public void before() { when(protocolContext.safeConsensusContext(Mockito.any())).thenReturn(Optional.of(mergeContext)); when(protocolContext.getBlockchain()).thenReturn(blockchain); - this.method = new EngineNewPayload(vertx, protocolContext, mergeCoordinator); + when(ethPeers.peerCount()).thenReturn(1); + this.method = new EngineNewPayload(vertx, protocolContext, mergeCoordinator, ethPeers); } @Test @@ -200,6 +207,24 @@ public void shouldReturnInvalidWithLatestValidHashIsABadBlock() { assertThat(res.getStatusAsString()).isEqualTo(INVALID.name()); } + @Test + public void shouldNotReturnInvalidOnInternalException() { + BlockHeader mockHeader = createBlockHeader(); + when(blockchain.getBlockByHash(mockHeader.getHash())).thenReturn(Optional.empty()); + when(blockchain.getBlockHeader(mockHeader.getParentHash())) + .thenReturn(Optional.of(mock(BlockHeader.class))); + when(mergeCoordinator.getLatestValidAncestor(any(BlockHeader.class))) + .thenReturn(Optional.of(mockHash)); + when(mergeCoordinator.latestValidAncestorDescendsFromTerminal(any(BlockHeader.class))) + .thenReturn(true); + when(mergeCoordinator.rememberBlock(any())) + .thenReturn(new Result("kablooey", new StorageException(new Exception()))); + + var resp = resp(mockPayload(mockHeader, Collections.emptyList())); + + fromErrorResp(resp); + } + @Test public void shouldReturnInvalidBlockHashOnBadHashParameter() { BlockHeader mockHeader = new BlockHeaderTestFixture().buildHeader(); @@ -355,6 +380,14 @@ private EnginePayloadStatusResult fromSuccessResp(final JsonRpcResponse resp) { .get(); } + private JsonRpcError fromErrorResp(final JsonRpcResponse resp) { + assertThat(resp.getType()).isEqualTo(JsonRpcResponseType.ERROR); + return Optional.of(resp) + .map(JsonRpcErrorResponse.class::cast) + .map(JsonRpcErrorResponse::getError) + .get(); + } + private BlockHeader createBlockHeader() { BlockHeader parentBlockHeader = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE).buildHeader(); diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractMiningCoordinator.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractMiningCoordinator.java index a54b1e8c109..e6baf7fa103 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractMiningCoordinator.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractMiningCoordinator.java @@ -95,11 +95,10 @@ public void start() { @Override public void stop() { synchronized (this) { - if (state != State.RUNNING) { - return; + if (state == State.RUNNING) { + haltCurrentMiningOperation(); } state = State.STOPPED; - haltCurrentMiningOperation(); executor.shutDown(); } } diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/BlockTransactionSelectorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/BlockTransactionSelectorTest.java index b64edffeb23..e5f6cdb995c 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/BlockTransactionSelectorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/BlockTransactionSelectorTest.java @@ -39,7 +39,7 @@ import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.core.TransactionTestFixture; import org.hyperledger.besu.ethereum.difficulty.fixed.FixedDifficultyProtocolSchedule; -import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.sorter.BaseFeePendingTransactionsSorter; import org.hyperledger.besu.ethereum.eth.transactions.sorter.GasPricePendingTransactionsSorter; import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor; @@ -60,6 +60,7 @@ import java.math.BigInteger; import java.time.Instant; +import java.time.ZoneId; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -82,12 +83,10 @@ public class BlockTransactionSelectorTest { private final Blockchain blockchain = new ReferenceTestBlockchain(); private final GasPricePendingTransactionsSorter pendingTransactions = new GasPricePendingTransactionsSorter( - TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, - 5, - TestClock.fixed(), + ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(5).build(), + TestClock.system(ZoneId.systemDefault()), metricsSystem, - BlockTransactionSelectorTest::mockBlockHeader, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP); + BlockTransactionSelectorTest::mockBlockHeader); private final MutableWorldState worldState = InMemoryKeyValueStorageProvider.createInMemoryWorldState(); @Mock private MainnetTransactionProcessor transactionProcessor; @@ -334,16 +333,14 @@ public void useSingleGasSpaceForAllTransactions() { final Address miningBeneficiary = AddressHelpers.ofValue(1); final BaseFeePendingTransactionsSorter pendingTransactions1559 = new BaseFeePendingTransactionsSorter( - TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, - 5, - TestClock.fixed(), + ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(5).build(), + TestClock.system(ZoneId.systemDefault()), metricsSystem, () -> { final BlockHeader mockBlockHeader = mock(BlockHeader.class); when(mockBlockHeader.getBaseFee()).thenReturn(Optional.of(Wei.ONE)); return mockBlockHeader; - }, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP); + }); final BlockTransactionSelector selector = new BlockTransactionSelector( transactionProcessor, diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreatorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreatorTest.java index b44eb0e5cc4..72921fdf3dc 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreatorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreatorTest.java @@ -28,7 +28,7 @@ import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; -import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.sorter.BaseFeePendingTransactionsSorter; import org.hyperledger.besu.ethereum.mainnet.EpochCalculator; import org.hyperledger.besu.ethereum.mainnet.PoWHasher; @@ -94,12 +94,10 @@ public void createMainnetBlock1() throws IOException { final BaseFeePendingTransactionsSorter pendingTransactions = new BaseFeePendingTransactionsSorter( - TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, - 1, + ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(1).build(), TestClock.fixed(), metricsSystem, - executionContextTestFixture.getProtocolContext().getBlockchain()::getChainHeadHeader, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP); + executionContextTestFixture.getProtocolContext().getBlockchain()::getChainHeadHeader); final PoWBlockCreator blockCreator = new PoWBlockCreator( @@ -155,12 +153,10 @@ public void createMainnetBlock1_fixedDifficulty1() { final BaseFeePendingTransactionsSorter pendingTransactions = new BaseFeePendingTransactionsSorter( - TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, - 1, + ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(1).build(), TestClock.fixed(), metricsSystem, - executionContextTestFixture.getProtocolContext().getBlockchain()::getChainHeadHeader, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP); + executionContextTestFixture.getProtocolContext().getBlockchain()::getChainHeadHeader); final PoWBlockCreator blockCreator = new PoWBlockCreator( @@ -211,12 +207,10 @@ public void rewardBeneficiary_zeroReward_skipZeroRewardsFalse() { final BaseFeePendingTransactionsSorter pendingTransactions = new BaseFeePendingTransactionsSorter( - TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, - 1, + ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(1).build(), TestClock.fixed(), metricsSystem, - executionContextTestFixture.getProtocolContext().getBlockchain()::getChainHeadHeader, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP); + executionContextTestFixture.getProtocolContext().getBlockchain()::getChainHeadHeader); final PoWBlockCreator blockCreator = new PoWBlockCreator( @@ -283,12 +277,10 @@ public void rewardBeneficiary_zeroReward_skipZeroRewardsTrue() { final BaseFeePendingTransactionsSorter pendingTransactions = new BaseFeePendingTransactionsSorter( - TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, - 1, + ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(1).build(), TestClock.fixed(), metricsSystem, - executionContextTestFixture.getProtocolContext().getBlockchain()::getChainHeadHeader, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP); + executionContextTestFixture.getProtocolContext().getBlockchain()::getChainHeadHeader); final PoWBlockCreator blockCreator = new PoWBlockCreator( diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWMinerExecutorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWMinerExecutorTest.java index d922a788bd6..6c4a9f1b31d 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWMinerExecutorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWMinerExecutorTest.java @@ -21,7 +21,7 @@ import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.MiningParameters; -import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.sorter.GasPricePendingTransactionsSorter; import org.hyperledger.besu.ethereum.mainnet.EpochCalculator; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; @@ -29,6 +29,7 @@ import org.hyperledger.besu.testutil.TestClock; import org.hyperledger.besu.util.Subscribers; +import java.time.ZoneId; import java.util.Optional; import org.junit.Test; @@ -43,12 +44,10 @@ public void startingMiningWithoutCoinbaseThrowsException() { final GasPricePendingTransactionsSorter pendingTransactions = new GasPricePendingTransactionsSorter( - TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, - 1, - TestClock.fixed(), + ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(1).build(), + TestClock.system(ZoneId.systemDefault()), metricsSystem, - PoWMinerExecutorTest::mockBlockHeader, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP); + PoWMinerExecutorTest::mockBlockHeader); final PoWMinerExecutor executor = new PoWMinerExecutor( @@ -72,12 +71,10 @@ public void settingCoinbaseToNullThrowsException() { final GasPricePendingTransactionsSorter pendingTransactions = new GasPricePendingTransactionsSorter( - TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, - 1, - TestClock.fixed(), + ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(1).build(), + TestClock.system(ZoneId.systemDefault()), metricsSystem, - PoWMinerExecutorTest::mockBlockHeader, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP); + PoWMinerExecutorTest::mockBlockHeader); final PoWMinerExecutor executor = new PoWMinerExecutor( diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/BlockValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/BlockValidator.java index 2ba11e583a9..363174da9d4 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/BlockValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/BlockValidator.java @@ -27,15 +27,24 @@ public interface BlockValidator { class Result { public final Optional blockProcessingOutputs; public final Optional errorMessage; + public final Optional cause; public Result(final BlockProcessingOutputs blockProcessingOutputs) { this.blockProcessingOutputs = Optional.of(blockProcessingOutputs); this.errorMessage = Optional.empty(); + this.cause = Optional.empty(); } public Result(final String errorMessage) { this.blockProcessingOutputs = Optional.empty(); this.errorMessage = Optional.of(errorMessage); + this.cause = Optional.empty(); + } + + public Result(final String errorMessage, final Throwable cause) { + this.blockProcessingOutputs = Optional.empty(); + this.errorMessage = Optional.of(errorMessage); + this.cause = Optional.of(cause); } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/MainnetBlockValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/MainnetBlockValidator.java index c1cd7d952a0..3add3c53472 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/MainnetBlockValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/MainnetBlockValidator.java @@ -110,7 +110,9 @@ public BlockValidator.Result validateAndProcessBlock( shouldPersist ? maybeWorldState.get() : maybeWorldState.get().copy(); final BlockProcessor.Result result = processBlock(context, worldState, block); - if (result.isFailed()) { + if (result.isFailed() && result.causedBy().isPresent()) { + return handleAndReportFailure(block, "Error processing block", result); + } else if (result.isFailed()) { return handleAndReportFailure(block, "Error processing block"); } @@ -144,6 +146,21 @@ private Result handleAndReportFailure(final Block invalidBlock, final String rea return new Result(reason); } + private Result handleAndReportFailure( + final Block invalidBlock, final String reason, final BlockProcessor.Result result) { + if (result.causedBy().isPresent()) { + LOG.error( + "{}. Block {}, caused by {}", reason, invalidBlock.toLogString(), result.causedBy()); + // TODO: if it's an internal error, don't add it + badBlockManager.addBadBlock(invalidBlock); + return new Result(reason, result.causedBy().get()); + } else { + LOG.error("{}. Block {}", reason, invalidBlock.toLogString()); + badBlockManager.addBadBlock(invalidBlock); + return new Result(reason); + } + } + /** * Processes a block, returning the result of the processing * diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiInMemoryWorldStateKeyValueStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiInMemoryWorldStateKeyValueStorage.java index f539b899da2..b4b5dcc44b6 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiInMemoryWorldStateKeyValueStorage.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiInMemoryWorldStateKeyValueStorage.java @@ -14,10 +14,13 @@ */ package org.hyperledger.besu.ethereum.bonsai; +import org.hyperledger.besu.ethereum.worldstate.PeerTrieNodeFinder; import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; +import java.util.Optional; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,8 +35,15 @@ public BonsaiInMemoryWorldStateKeyValueStorage( final KeyValueStorage codeStorage, final KeyValueStorage storageStorage, final KeyValueStorage trieBranchStorage, - final KeyValueStorage trieLogStorage) { - super(accountStorage, codeStorage, storageStorage, trieBranchStorage, trieLogStorage); + final KeyValueStorage trieLogStorage, + final Optional fallbackNodeFinder) { + super( + accountStorage, + codeStorage, + storageStorage, + trieBranchStorage, + trieLogStorage, + fallbackNodeFinder); } @Override diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiLayeredWorldState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiLayeredWorldState.java index 4d76ff4aefa..7ea4fa402e6 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiLayeredWorldState.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiLayeredWorldState.java @@ -259,14 +259,15 @@ public Stream streamAccounts(final Bytes32 startKeyHash, fina public MutableWorldState copy() { final BonsaiPersistedWorldState bonsaiPersistedWorldState = ((BonsaiPersistedWorldState) archive.getMutable()); - return new BonsaiInMemoryWorldState( - archive, + BonsaiInMemoryWorldStateKeyValueStorage bonsaiInMemoryWorldStateKeyValueStorage = new BonsaiInMemoryWorldStateKeyValueStorage( bonsaiPersistedWorldState.getWorldStateStorage().accountStorage, bonsaiPersistedWorldState.getWorldStateStorage().codeStorage, bonsaiPersistedWorldState.getWorldStateStorage().storageStorage, bonsaiPersistedWorldState.getWorldStateStorage().trieBranchStorage, - bonsaiPersistedWorldState.getWorldStateStorage().trieLogStorage)); + bonsaiPersistedWorldState.getWorldStateStorage().trieLogStorage, + bonsaiPersistedWorldState.getWorldStateStorage().getMaybeFallbackNodeFinder()); + return new BonsaiInMemoryWorldState(archive, bonsaiInMemoryWorldStateKeyValueStorage); } @Override diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiPersistedWorldState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiPersistedWorldState.java index cafe2f48fba..049ba1ee230 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiPersistedWorldState.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiPersistedWorldState.java @@ -75,14 +75,16 @@ public BonsaiWorldStateArchive getArchive() { @Override public MutableWorldState copy() { - return new BonsaiInMemoryWorldState( - archive, + BonsaiInMemoryWorldStateKeyValueStorage bonsaiInMemoryWorldStateKeyValueStorage = new BonsaiInMemoryWorldStateKeyValueStorage( worldStateStorage.accountStorage, worldStateStorage.codeStorage, worldStateStorage.storageStorage, worldStateStorage.trieBranchStorage, - worldStateStorage.trieLogStorage)); + worldStateStorage.trieLogStorage, + getWorldStateStorage().getMaybeFallbackNodeFinder()); + + return new BonsaiInMemoryWorldState(archive, bonsaiInMemoryWorldStateKeyValueStorage); } @Override diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchive.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchive.java index 853ab83492a..e0ac5d253f5 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchive.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchive.java @@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.bonsai; +import static com.google.common.base.Preconditions.checkNotNull; import static org.hyperledger.besu.datatypes.Hash.fromPlugin; import org.hyperledger.besu.datatypes.Address; @@ -26,6 +27,7 @@ import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.proof.WorldStateProof; import org.hyperledger.besu.ethereum.storage.StorageProvider; +import org.hyperledger.besu.ethereum.worldstate.PeerTrieNodeFinder; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.evm.worldstate.WorldState; @@ -243,4 +245,9 @@ public Optional getAccountProof( // FIXME we can do proofs for layered tries and the persisted trie return Optional.empty(); } + + public void useFallbackNodeFinder(final Optional fallbackNodeFinder) { + checkNotNull(fallbackNodeFinder); + worldStateStorage.useFallbackNodeFinder(fallbackNodeFinder); + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorage.java index 29b52265177..e5fc6b752d3 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorage.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorage.java @@ -14,12 +14,15 @@ */ package org.hyperledger.besu.ethereum.bonsai; +import static com.google.common.base.Preconditions.checkNotNull; + import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; import org.hyperledger.besu.ethereum.trie.MerklePatriciaTrie; import org.hyperledger.besu.ethereum.trie.StoredMerklePatriciaTrie; import org.hyperledger.besu.ethereum.trie.StoredNodeFactory; +import org.hyperledger.besu.ethereum.worldstate.PeerTrieNodeFinder; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; @@ -46,16 +49,16 @@ public class BonsaiWorldStateKeyValueStorage implements WorldStateStorage { protected final KeyValueStorage trieBranchStorage; protected final KeyValueStorage trieLogStorage; + private Optional maybeFallbackNodeFinder; + public BonsaiWorldStateKeyValueStorage(final StorageProvider provider) { - accountStorage = - provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE); - codeStorage = provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.CODE_STORAGE); - storageStorage = - provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.ACCOUNT_STORAGE_STORAGE); - trieBranchStorage = - provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE); - trieLogStorage = - provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.TRIE_LOG_STORAGE); + this( + provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE), + provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.CODE_STORAGE), + provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.ACCOUNT_STORAGE_STORAGE), + provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE), + provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.TRIE_LOG_STORAGE), + Optional.empty()); } public BonsaiWorldStateKeyValueStorage( @@ -64,11 +67,28 @@ public BonsaiWorldStateKeyValueStorage( final KeyValueStorage storageStorage, final KeyValueStorage trieBranchStorage, final KeyValueStorage trieLogStorage) { + this( + accountStorage, + codeStorage, + storageStorage, + trieBranchStorage, + trieLogStorage, + Optional.empty()); + } + + public BonsaiWorldStateKeyValueStorage( + final KeyValueStorage accountStorage, + final KeyValueStorage codeStorage, + final KeyValueStorage storageStorage, + final KeyValueStorage trieBranchStorage, + final KeyValueStorage trieLogStorage, + final Optional fallbackNodeFinder) { this.accountStorage = accountStorage; this.codeStorage = codeStorage; this.storageStorage = storageStorage; this.trieBranchStorage = trieBranchStorage; this.trieLogStorage = trieLogStorage; + this.maybeFallbackNodeFinder = fallbackNodeFinder; } @Override @@ -104,7 +124,17 @@ public Optional getAccountStateTrieNode(final Bytes location, final Bytes if (nodeHash.equals(MerklePatriciaTrie.EMPTY_TRIE_NODE_HASH)) { return Optional.of(MerklePatriciaTrie.EMPTY_TRIE_NODE); } else { - return trieBranchStorage.get(location.toArrayUnsafe()).map(Bytes::wrap); + final Optional value = + trieBranchStorage.get(location.toArrayUnsafe()).map(Bytes::wrap); + if (value.isPresent()) { + return value + .filter(b -> Hash.hash(b).equals(nodeHash)) + .or( + () -> + maybeFallbackNodeFinder.flatMap( + finder -> finder.getAccountStateTrieNode(location, nodeHash))); + } + return Optional.empty(); } } @@ -114,9 +144,20 @@ public Optional getAccountStorageTrieNode( if (nodeHash.equals(MerklePatriciaTrie.EMPTY_TRIE_NODE_HASH)) { return Optional.of(MerklePatriciaTrie.EMPTY_TRIE_NODE); } else { - return trieBranchStorage - .get(Bytes.concatenate(accountHash, location).toArrayUnsafe()) - .map(Bytes::wrap); + final Optional value = + trieBranchStorage + .get(Bytes.concatenate(accountHash, location).toArrayUnsafe()) + .map(Bytes::wrap); + if (value.isPresent()) { + return value + .filter(b -> Hash.hash(b).equals(nodeHash)) + .or( + () -> + maybeFallbackNodeFinder.flatMap( + finder -> + finder.getAccountStorageTrieNode(accountHash, location, nodeHash))); + } + return Optional.empty(); } } @@ -218,6 +259,15 @@ public void removeNodeAddedListener(final long id) { throw new RuntimeException("removeNodeAddedListener not available"); } + public Optional getMaybeFallbackNodeFinder() { + return maybeFallbackNodeFinder; + } + + public void useFallbackNodeFinder(final Optional maybeFallbackNodeFinder) { + checkNotNull(maybeFallbackNodeFinder); + this.maybeFallbackNodeFinder = maybeFallbackNodeFinder; + } + public static class Updater implements WorldStateStorage.Updater { private final KeyValueStorageTransaction accountStorageTransaction; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java index 0e09be2f647..63da03675ab 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java @@ -34,6 +34,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Optional; import com.google.common.collect.ImmutableList; import org.slf4j.Logger; @@ -62,6 +63,8 @@ public static class Result implements BlockProcessor.Result { private final boolean successful; + private Optional causedBy = Optional.empty(); + private final List receipts; private final List privateReceipts; @@ -83,6 +86,17 @@ public static AbstractBlockProcessor.Result failed() { return FAILED; } + public static AbstractBlockProcessor.Result failedBecause(final Throwable t) { + return new AbstractBlockProcessor.Result(t); + } + + Result(final Throwable t) { + this.successful = false; + this.receipts = null; + this.privateReceipts = Collections.emptyList(); + this.causedBy = Optional.of(t); + } + Result(final boolean successful, final List receipts) { this.successful = successful; this.receipts = receipts; @@ -112,6 +126,11 @@ public List getPrivateReceipts() { public boolean isSuccessful() { return successful; } + + @Override + public Optional causedBy() { + return this.causedBy; + } } protected final MainnetTransactionProcessor transactionProcessor; @@ -201,7 +220,7 @@ public AbstractBlockProcessor.Result processBlock( worldState.persist(blockHeader); } catch (Exception e) { LOG.error("failed persisting block", e); - return AbstractBlockProcessor.Result.failed(); + return AbstractBlockProcessor.Result.failedBecause(e); } return AbstractBlockProcessor.Result.successful(receipts); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BlockProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BlockProcessor.java index 94e63fcf843..22963082c61 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BlockProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BlockProcessor.java @@ -24,6 +24,7 @@ import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater; import java.util.List; +import java.util.Optional; /** Processes a block. */ public interface BlockProcessor { @@ -60,6 +61,10 @@ interface Result { default boolean isFailed() { return !isSuccessful(); } + + default Optional causedBy() { + return Optional.empty(); + } } /** diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/PeerTrieNodeFinder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/PeerTrieNodeFinder.java new file mode 100644 index 00000000000..549157860bd --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/PeerTrieNodeFinder.java @@ -0,0 +1,29 @@ +/* + * 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.ethereum.worldstate; + +import org.hyperledger.besu.datatypes.Hash; + +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +public interface PeerTrieNodeFinder { + + Optional getAccountStateTrieNode(final Bytes location, final Bytes32 nodeHash); + + Optional getAccountStorageTrieNode(Hash accountHash, Bytes location, Bytes32 nodeHash); +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorageTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorageTest.java index ea7804c8bbc..9e508148ee1 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorageTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorageTest.java @@ -18,9 +18,11 @@ import static org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage.WORLD_ROOT_HASH_KEY; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; @@ -30,8 +32,10 @@ import org.hyperledger.besu.ethereum.trie.MerklePatriciaTrie; import org.hyperledger.besu.ethereum.trie.StorageEntriesCollector; import org.hyperledger.besu.ethereum.trie.StoredMerklePatriciaTrie; +import org.hyperledger.besu.ethereum.worldstate.PeerTrieNodeFinder; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; +import java.util.Optional; import java.util.TreeMap; import org.apache.tuweni.bytes.Bytes; @@ -291,6 +295,62 @@ public void isWorldStateAvailable_afterCallingSaveWorldstate() { assertThat(storage.isWorldStateAvailable(Bytes32.wrap(nodeHashKey), Hash.EMPTY)).isTrue(); } + @Test + public void getAccountStateTrieNode_callFallbackMechanismForInvalidNode() { + + PeerTrieNodeFinder peerTrieNodeFinder = mock(PeerTrieNodeFinder.class); + + final Bytes location = Bytes.fromHexString("0x01"); + final Bytes bytesInDB = Bytes.fromHexString("0x123456"); + + final Hash hashToFind = Hash.hash(Bytes.of(1)); + final Bytes bytesToFind = Bytes.fromHexString("0x123457"); + + final BonsaiWorldStateKeyValueStorage storage = emptyStorage(); + + when(peerTrieNodeFinder.getAccountStateTrieNode(location, hashToFind)) + .thenReturn(Optional.of(bytesToFind)); + storage.useFallbackNodeFinder(Optional.of(peerTrieNodeFinder)); + + storage.updater().putAccountStateTrieNode(location, Hash.hash(bytesInDB), bytesInDB).commit(); + + Optional accountStateTrieNodeResult = + storage.getAccountStateTrieNode(location, hashToFind); + + verify(peerTrieNodeFinder).getAccountStateTrieNode(location, hashToFind); + assertThat(accountStateTrieNodeResult).contains(bytesToFind); + } + + @Test + public void getAccountStorageTrieNode_callFallbackMechanismForInvalidNode() { + + PeerTrieNodeFinder peerTrieNodeFinder = mock(PeerTrieNodeFinder.class); + + final Hash account = Hash.hash(Bytes32.ZERO); + final Bytes location = Bytes.fromHexString("0x01"); + final Bytes bytesInDB = Bytes.fromHexString("0x123456"); + + final Hash hashToFind = Hash.hash(Bytes.of(1)); + final Bytes bytesToFind = Bytes.fromHexString("0x123457"); + + final BonsaiWorldStateKeyValueStorage storage = emptyStorage(); + + when(peerTrieNodeFinder.getAccountStorageTrieNode(account, location, hashToFind)) + .thenReturn(Optional.of(bytesToFind)); + storage.useFallbackNodeFinder(Optional.of(peerTrieNodeFinder)); + + storage + .updater() + .putAccountStorageTrieNode(account, location, Hash.hash(bytesInDB), bytesInDB) + .commit(); + + Optional accountStateTrieNodeResult = + storage.getAccountStorageTrieNode(account, location, hashToFind); + + verify(peerTrieNodeFinder).getAccountStorageTrieNode(account, location, hashToFind); + assertThat(accountStateTrieNodeResult).contains(bytesToFind); + } + private BonsaiWorldStateKeyValueStorage emptyStorage() { return new BonsaiWorldStateKeyValueStorage(new InMemoryKeyValueStorageProvider()); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthPeers.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthPeers.java index b45ab54a17d..dc6eead7920 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthPeers.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthPeers.java @@ -203,7 +203,9 @@ public Stream streamAllPeers() { } public Stream streamAvailablePeers() { - return streamAllPeers().filter(EthPeer::readyForRequests); + return streamAllPeers() + .filter(EthPeer::readyForRequests) + .filter(peer -> !peer.isDisconnected()); } public Stream streamBestPeers() { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManager.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManager.java index 3956ce1cf29..1dc80b10230 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManager.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManager.java @@ -257,7 +257,7 @@ public void processMessage(final Capability cap, final Message message) { } else if (!ethPeer.statusHasBeenReceived()) { // Peers are required to send status messages before any other message type LOG.debug( - "{} requires a Status ({}) message to be sent first. Instead, received message {}. Disconnecting from {}.", + "{} requires a Status ({}) message to be sent first. Instead, received message {} (BREACH_OF_PROTOCOL). Disconnecting from {}.", this.getClass().getSimpleName(), EthPV62.STATUS, code, @@ -277,7 +277,9 @@ public void processMessage(final Capability cap, final Message message) { final EthMessage ethMessage = new EthMessage(ethPeer, messageData); if (!ethPeer.validateReceivedMessage(ethMessage, getSupportedProtocol())) { - LOG.debug("Unsolicited message received, disconnecting from EthPeer: {}", ethPeer); + LOG.debug( + "Unsolicited message received (BREACH_OF_PROTOCOL), disconnecting from EthPeer: {}", + ethPeer); ethPeer.disconnect(DisconnectReason.BREACH_OF_PROTOCOL); return; } @@ -308,7 +310,10 @@ public void processMessage(final Capability cap, final Message message) { } } catch (final RLPException e) { LOG.debug( - "Received malformed message {} , disconnecting: {}", messageData.getData(), ethPeer, e); + "Received malformed message {} (BREACH_OF_PROTOCOL), disconnecting: {}", + messageData.getData(), + ethPeer, + e); ethPeer.disconnect(DisconnectMessage.DisconnectReason.BREACH_OF_PROTOCOL); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/PendingPeerRequest.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/PendingPeerRequest.java index 57b0d9b5cc1..120b32bdfe3 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/PendingPeerRequest.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/PendingPeerRequest.java @@ -52,18 +52,18 @@ public boolean attemptExecution() { if (result.isDone()) { return true; } - final Optional leastBusySuitablePeer = getLeastBusySuitablePeer(); - if (!leastBusySuitablePeer.isPresent()) { + final Optional maybePeer = getPeerToUse(); + if (maybePeer.isEmpty()) { // No peers have the required height. result.completeExceptionally(new NoAvailablePeersException()); return true; } else { - // At least one peer has the required height, but we not be able to use it if it's busy - final Optional selectedPeer = - leastBusySuitablePeer.filter(EthPeer::hasAvailableRequestCapacity); + // At least one peer has the required height, but we are not able to use it if it's busy + final Optional maybePeerWithCapacity = + maybePeer.filter(EthPeer::hasAvailableRequestCapacity); - selectedPeer.ifPresent(this::sendRequest); - return selectedPeer.isPresent(); + maybePeerWithCapacity.ifPresent(this::sendRequest); + return maybePeerWithCapacity.isPresent(); } } @@ -79,8 +79,9 @@ private synchronized void sendRequest(final EthPeer peer) { } } - private Optional getLeastBusySuitablePeer() { - return peer.isPresent() + private Optional getPeerToUse() { + // return the assigned peer if still valid, otherwise switch to another peer + return peer.filter(p -> !p.isDisconnected()).isPresent() ? peer : ethPeers .streamAvailablePeers() diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/RequestManager.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/RequestManager.java index 800836df9a8..cefcdab1ff7 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/RequestManager.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/RequestManager.java @@ -17,6 +17,7 @@ import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection.PeerNotConnected; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage; +import org.hyperledger.besu.ethereum.rlp.RLPException; import java.math.BigInteger; import java.util.Collection; @@ -29,7 +30,12 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class RequestManager { + + private static final Logger LOG = LoggerFactory.getLogger(RequestManager.class); private final AtomicLong requestIdCounter = new AtomicLong(0); private final Map responseStreams = new ConcurrentHashMap<>(); private final EthPeer peer; @@ -65,19 +71,34 @@ public ResponseStream dispatchRequest(final RequestSender sender, final MessageD public void dispatchResponse(final EthMessage ethMessage) { final Collection streams = List.copyOf(responseStreams.values()); final int count = outstandingRequests.decrementAndGet(); - if (supportsRequestId) { - // If there's a requestId, find the specific stream it belongs to - final Map.Entry requestIdAndEthMessage = - ethMessage.getData().unwrapMessageData(); - Optional.ofNullable(responseStreams.get(requestIdAndEthMessage.getKey())) - .ifPresentOrElse( - responseStream -> responseStream.processMessage(requestIdAndEthMessage.getValue()), - // disconnect on incorrect requestIds - () -> peer.disconnect(DisconnectMessage.DisconnectReason.BREACH_OF_PROTOCOL)); - } else { - // otherwise iterate through all of them - streams.forEach(stream -> stream.processMessage(ethMessage.getData())); + try { + if (supportsRequestId) { + // If there's a requestId, find the specific stream it belongs to + final Map.Entry requestIdAndEthMessage = + ethMessage.getData().unwrapMessageData(); + Optional.ofNullable(responseStreams.get(requestIdAndEthMessage.getKey())) + .ifPresentOrElse( + responseStream -> responseStream.processMessage(requestIdAndEthMessage.getValue()), + // disconnect on incorrect requestIds + () -> { + LOG.debug( + "Request ID incorrect (BREACH_OF_PROTOCOL), disconnecting peer {}", peer); + peer.disconnect(DisconnectMessage.DisconnectReason.BREACH_OF_PROTOCOL); + }); + } else { + // otherwise iterate through all of them + streams.forEach(stream -> stream.processMessage(ethMessage.getData())); + } + } catch (final RLPException e) { + LOG.debug( + "Received malformed message {} (BREACH_OF_PROTOCOL), disconnecting: {}", + ethMessage.getData(), + peer, + e); + + peer.disconnect(DisconnectMessage.DisconnectReason.BREACH_OF_PROTOCOL); } + if (count == 0) { // No possibility of any remaining outstanding messages closeOutstandingStreams(streams); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractGetHeadersFromPeerTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractGetHeadersFromPeerTask.java index 0fec6bf9806..58d5800f11d 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractGetHeadersFromPeerTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractGetHeadersFromPeerTask.java @@ -109,7 +109,8 @@ protected Optional> processResponse( final BlockHeader child = reverse ? prevBlockHeader : header; if (!parent.getHash().equals(child.getParentHash())) { LOG.debug( - "Sequential headers must form a chain through hashes, disconnecting peer: {}", peer); + "Sequential headers must form a chain through hashes (BREACH_OF_PROTOCOL), disconnecting peer: {}", + peer); peer.disconnect(DisconnectMessage.DisconnectReason.BREACH_OF_PROTOCOL); return Optional.empty(); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractRetryingPeerTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractRetryingPeerTask.java index 30033200355..cf48d69847d 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractRetryingPeerTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractRetryingPeerTask.java @@ -114,9 +114,9 @@ protected void handleTaskError(final Throwable error) { if (cause instanceof NoAvailablePeersException) { LOG.debug( - "No useful peer found, checking remaining current peers for usefulness: {}", + "No useful peer found, wait max 5 seconds for new peer to connect: current peers {}", ethContext.getEthPeers().peerCount()); - // Wait for new peer to connect + final WaitForPeerTask waitTask = WaitForPeerTask.create(ethContext, metricsSystem); executeSubTask( () -> diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractRetryingSwitchingPeerTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractRetryingSwitchingPeerTask.java index d9724895e66..13751adcdf8 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractRetryingSwitchingPeerTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractRetryingSwitchingPeerTask.java @@ -131,7 +131,7 @@ private Stream remainingPeersToTry() { return getEthContext() .getEthPeers() .streamBestPeers() - .filter(peer -> !peer.isDisconnected() && !triedPeers.contains(peer)); + .filter(peer -> !triedPeers.contains(peer)); } private void refreshPeers() { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/BlockPropagationManager.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/BlockPropagationManager.java index e7f5986a666..20355f474d4 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/BlockPropagationManager.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/BlockPropagationManager.java @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.ethereum.eth.sync; +import static org.hyperledger.besu.util.FutureUtils.exceptionallyCompose; +import static org.hyperledger.besu.util.Slf4jLambdaHelper.debugLambda; import static org.hyperledger.besu.util.Slf4jLambdaHelper.traceLambda; import org.hyperledger.besu.consensus.merge.ForkchoiceMessageListener; @@ -23,6 +25,7 @@ import org.hyperledger.besu.ethereum.chain.BlockAddedEvent; import org.hyperledger.besu.ethereum.chain.BlockAddedEvent.EventType; import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Difficulty; @@ -46,6 +49,7 @@ import org.hyperledger.besu.ethereum.rlp.RLPException; import org.hyperledger.besu.plugin.services.MetricsSystem; +import java.time.Duration; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -53,8 +57,10 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -76,12 +82,9 @@ public class BlockPropagationManager implements ForkchoiceMessageListener { private final BlockBroadcaster blockBroadcaster; private final AtomicBoolean started = new AtomicBoolean(false); - - private final Set importingBlocks = Collections.newSetFromMap(new ConcurrentHashMap<>()); - private final Set requestedBlocks = Collections.newSetFromMap(new ConcurrentHashMap<>()); - private final Set requestedNonAnnouncedBlocks = - Collections.newSetFromMap(new ConcurrentHashMap<>()); + private final ProcessingBlocksManager processingBlocksManager; private final PendingBlocksManager pendingBlocksManager; + private final Duration getBlockTimeoutMillis; private Optional onBlockAddedSId = Optional.empty(); private Optional newBlockSId; private Optional newBlockHashesSId; @@ -95,6 +98,28 @@ public class BlockPropagationManager implements ForkchoiceMessageListener { final PendingBlocksManager pendingBlocksManager, final MetricsSystem metricsSystem, final BlockBroadcaster blockBroadcaster) { + this( + config, + protocolSchedule, + protocolContext, + ethContext, + syncState, + pendingBlocksManager, + metricsSystem, + blockBroadcaster, + new ProcessingBlocksManager()); + } + + BlockPropagationManager( + final SynchronizerConfiguration config, + final ProtocolSchedule protocolSchedule, + final ProtocolContext protocolContext, + final EthContext ethContext, + final SyncState syncState, + final PendingBlocksManager pendingBlocksManager, + final MetricsSystem metricsSystem, + final BlockBroadcaster blockBroadcaster, + final ProcessingBlocksManager processingBlocksManager) { this.config = config; this.protocolSchedule = protocolSchedule; this.protocolContext = protocolContext; @@ -104,6 +129,9 @@ public class BlockPropagationManager implements ForkchoiceMessageListener { this.syncState = syncState; this.pendingBlocksManager = pendingBlocksManager; this.syncState.subscribeTTDReached(this::reactToTTDReachedEvent); + this.getBlockTimeoutMillis = + Duration.ofMillis(config.getPropagationManagerGetBlockTimeoutMillis()); + this.processingBlocksManager = processingBlocksManager; } public void start() { @@ -156,6 +184,7 @@ private void clearListeners() { private void onBlockAdded(final BlockAddedEvent blockAddedEvent) { // Check to see if any of our pending blocks are now ready for import final Block newBlock = blockAddedEvent.getBlock(); + traceLambda( LOG, "Block added event type {} for block {}. Current status {}", @@ -247,7 +276,7 @@ private void maybeProcessNonAnnouncedBlocks(final Block newBlock) { if (distance < config.getBlockPropagationRange().upperEndpoint() && minAnnouncedBlockNumber > firstNonAnnouncedBlockNumber) { - if (requestedNonAnnouncedBlocks.add(firstNonAnnouncedBlockNumber)) { + if (processingBlocksManager.addNonAnnouncedBlocks(firstNonAnnouncedBlockNumber)) { retrieveNonAnnouncedBlock(firstNonAnnouncedBlockNumber); } } @@ -296,7 +325,7 @@ private void handleNewBlockFromNetwork(final EthMessage message) { importOrSavePendingBlock(block, message.getPeer().nodeId()); } catch (final RLPException e) { LOG.debug( - "Malformed NEW_BLOCK message received from peer, disconnecting: {}", + "Malformed NEW_BLOCK message received from peer (BREACH_OF_PROTOCOL), disconnecting: {}", message.getPeer(), e); message.getPeer().disconnect(DisconnectReason.BREACH_OF_PROTOCOL); @@ -338,7 +367,7 @@ private void handleNewBlockHashesFromNetwork(final EthMessage message) { LOG.trace("New block hash from network {} is already pending", announcedBlock); continue; } - if (importingBlocks.contains(announcedBlock.hash())) { + if (processingBlocksManager.alreadyImporting(announcedBlock.hash())) { LOG.trace("New block hash from network {} is already importing", announcedBlock); continue; } @@ -346,7 +375,7 @@ private void handleNewBlockHashesFromNetwork(final EthMessage message) { LOG.trace("New block hash from network {} was already imported", announcedBlock); continue; } - if (requestedBlocks.add(announcedBlock.hash())) { + if (processingBlocksManager.addRequestedBlock(announcedBlock.hash())) { newBlocks.add(announcedBlock); } else { LOG.trace("New block hash from network {} was already requested", announcedBlock); @@ -359,7 +388,7 @@ private void handleNewBlockHashesFromNetwork(final EthMessage message) { } } catch (final RLPException e) { LOG.debug( - "Malformed NEW_BLOCK_HASHES message received from peer, disconnecting: {}", + "Malformed NEW_BLOCK_HASHES message received from peer (BREACH_OF_PROTOCOL), disconnecting: {}", message.getPeer(), e); message.getPeer().disconnect(DisconnectReason.BREACH_OF_PROTOCOL); @@ -379,7 +408,7 @@ private CompletableFuture processAnnouncedBlock( private void requestParentBlock(final Block block) { final BlockHeader blockHeader = block.getHeader(); - if (requestedBlocks.add(blockHeader.getParentHash())) { + if (processingBlocksManager.addRequestedBlock(blockHeader.getParentHash())) { retrieveParentBlock(blockHeader); } else { LOG.debug("Parent block with hash {} is already requested", blockHeader.getParentHash()); @@ -397,34 +426,115 @@ private CompletableFuture retrieveParentBlock(final BlockHeader blockHead private CompletableFuture getBlockFromPeers( final Optional preferredPeer, final long blockNumber, - final Optional blockHash) { + final Optional maybeBlockHash) { + return repeatableGetBlockFromPeer(preferredPeer, blockNumber, maybeBlockHash) + .whenComplete( + (block, throwable) -> { + if (block != null) { + debugLambda(LOG, "Successfully retrieved block {}", block::toLogString); + processingBlocksManager.registerReceivedBlock(block); + } else { + if (throwable != null) { + LOG.warn( + "Failed to retrieve block " + + logBlockNumberMaybeHash(blockNumber, maybeBlockHash), + throwable); + } else { + // this could happen if we give up at some point since we find that it make no + // sense to retry + debugLambda( + LOG, + "Block {} not retrieved", + () -> logBlockNumberMaybeHash(blockNumber, maybeBlockHash)); + } + processingBlocksManager.registerFailedGetBlock(blockNumber, maybeBlockHash); + } + }); + } + + private CompletableFuture repeatableGetBlockFromPeer( + final Optional preferredPeer, + final long blockNumber, + final Optional maybeBlockHash) { + return exceptionallyCompose( + scheduleGetBlockFromPeers(preferredPeer, blockNumber, maybeBlockHash), + handleGetBlockErrors(blockNumber, maybeBlockHash)) + .thenCompose(r -> maybeRepeatGetBlock(blockNumber, maybeBlockHash)); + } + + private Function> handleGetBlockErrors( + final long blockNumber, final Optional maybeBlockHash) { + return throwable -> { + debugLambda( + LOG, + "Temporary failure retrieving block {} from peers with error {}", + () -> logBlockNumberMaybeHash(blockNumber, maybeBlockHash), + throwable::toString); + return CompletableFuture.completedFuture(null); + }; + } + + private CompletableFuture maybeRepeatGetBlock( + final long blockNumber, final Optional maybeBlockHash) { + final MutableBlockchain blockchain = protocolContext.getBlockchain(); + final Optional maybeBlock = + maybeBlockHash + .map(hash -> blockchain.getBlockByHash(hash)) + .orElseGet(() -> blockchain.getBlockByNumber(blockNumber)); + + // check if we got this block by other means + if (maybeBlock.isPresent()) { + final Block block = maybeBlock.get(); + debugLambda( + LOG, "No need to retry to get block {} since it is already present", block::toLogString); + return CompletableFuture.completedFuture(block); + } + + final long localChainHeight = blockchain.getChainHeadBlockNumber(); + final long bestChainHeight = syncState.bestChainHeight(localChainHeight); + if (!shouldImportBlockAtHeight(blockNumber, localChainHeight, bestChainHeight)) { + debugLambda( + LOG, + "Not retrying to get block {} since we are too far from local chain head {}", + () -> logBlockNumberMaybeHash(blockNumber, maybeBlockHash), + blockchain.getChainHead()::toLogString); + return CompletableFuture.completedFuture(null); + } + + debugLambda( + LOG, + "Retrying to get block {}", + () -> logBlockNumberMaybeHash(blockNumber, maybeBlockHash)); + + return ethContext + .getScheduler() + .scheduleSyncWorkerTask( + () -> repeatableGetBlockFromPeer(Optional.empty(), blockNumber, maybeBlockHash)); + } + + private CompletableFuture scheduleGetBlockFromPeers( + final Optional maybePreferredPeer, + final long blockNumber, + final Optional maybeBlockHash) { final RetryingGetBlockFromPeersTask getBlockTask = RetryingGetBlockFromPeersTask.create( protocolSchedule, ethContext, metricsSystem, - ethContext.getEthPeers().getMaxPeers(), - blockHash, + Math.max(1, ethContext.getEthPeers().peerCount()), + maybeBlockHash, blockNumber); - preferredPeer.ifPresent(getBlockTask::assignPeer); + maybePreferredPeer.ifPresent(getBlockTask::assignPeer); - return ethContext - .getScheduler() - .scheduleSyncWorkerTask(getBlockTask::run) - .thenCompose(r -> importOrSavePendingBlock(r.getResult(), r.getPeer().nodeId())) - .whenComplete( - (r, t) -> { - requestedNonAnnouncedBlocks.remove(blockNumber); - blockHash.ifPresentOrElse( - requestedBlocks::remove, - () -> { - if (r != null) { - // in case we successfully retrieved only by block number, when can remove - // the request by hash too - requestedBlocks.remove(r.getHash()); - } - }); - }); + var future = + ethContext + .getScheduler() + .scheduleSyncWorkerTask(getBlockTask::run) + .thenCompose(r -> importOrSavePendingBlock(r.getResult(), r.getPeer().nodeId())); + + ethContext.getScheduler().failAfterTimeout(future, getBlockTimeoutMillis); + + return future; } private void broadcastBlock(final Block block, final BlockHeader parent) { @@ -453,14 +563,14 @@ CompletableFuture importOrSavePendingBlock(final Block block, final Bytes return CompletableFuture.completedFuture(block); } - if (!importingBlocks.add(block.getHash())) { + if (!processingBlocksManager.addImportingBlock(block.getHash())) { traceLambda(LOG, "We're already importing this block {}", block::toLogString); return CompletableFuture.completedFuture(block); } if (protocolContext.getBlockchain().contains(block.getHash())) { traceLambda(LOG, "We've already imported this block {}", block::toLogString); - importingBlocks.remove(block.getHash()); + processingBlocksManager.registerBlockImportDone(block.getHash()); return CompletableFuture.completedFuture(block); } @@ -537,7 +647,7 @@ private CompletableFuture validateAndProcessPendingBlock( ethContext.getScheduler().scheduleSyncWorkerTask(() -> broadcastBlock(block, parent)); return runImportTask(block); } else { - importingBlocks.remove(block.getHash()); + processingBlocksManager.registerBlockImportDone(block.getHash()); badBlockManager.addBadBlock(block); LOG.warn("Failed to import announced block {}", block.toLogString()); return CompletableFuture.completedFuture(block); @@ -557,7 +667,7 @@ private CompletableFuture runImportTask(final Block block) { .run() .whenComplete( (result, throwable) -> { - importingBlocks.remove(block.getHash()); + processingBlocksManager.registerBlockImportDone(block.getHash()); if (throwable != null) { LOG.warn("Failed to import announced block {}", block.toLogString()); } @@ -591,17 +701,17 @@ private void reactToTTDReachedEvent(final boolean ttdReached) { @Override public String toString() { return "BlockPropagationManager{" - + "requestedBlocks=" - + requestedBlocks - + ", requestedNonAnnounceBlocks=" - + requestedNonAnnouncedBlocks - + ", importingBlocks=" - + importingBlocks + + processingBlocksManager + ", pendingBlocksManager=" + pendingBlocksManager + '}'; } + private String logBlockNumberMaybeHash( + final long blockNumber, final Optional maybeBlockHash) { + return blockNumber + maybeBlockHash.map(h -> " (" + h + ")").orElse(""); + } + @Override public void onNewForkchoiceMessage( final Hash headBlockHash, @@ -611,4 +721,54 @@ public void onNewForkchoiceMessage( stop(); } } + + static class ProcessingBlocksManager { + private final Set importingBlocks = Collections.newSetFromMap(new ConcurrentHashMap<>()); + private final Set requestedBlocks = Collections.newSetFromMap(new ConcurrentHashMap<>()); + private final Set requestedNonAnnouncedBlocks = + Collections.newSetFromMap(new ConcurrentHashMap<>()); + + boolean addRequestedBlock(final Hash hash) { + return requestedBlocks.add(hash); + } + + public boolean addNonAnnouncedBlocks(final long blockNumber) { + return requestedNonAnnouncedBlocks.add(blockNumber); + } + + public boolean alreadyImporting(final Hash hash) { + return importingBlocks.contains(hash); + } + + public synchronized void registerReceivedBlock(final Block block) { + requestedBlocks.remove(block.getHash()); + requestedNonAnnouncedBlocks.remove(block.getHeader().getNumber()); + } + + public synchronized void registerFailedGetBlock( + final long blockNumber, final Optional maybeBlockHash) { + requestedNonAnnouncedBlocks.remove(blockNumber); + maybeBlockHash.ifPresent(requestedBlocks::remove); + } + + public boolean addImportingBlock(final Hash hash) { + return importingBlocks.add(hash); + } + + public void registerBlockImportDone(final Hash hash) { + importingBlocks.remove(hash); + } + + @Override + public synchronized String toString() { + return "ProcessingBlocksManager{" + + "importingBlocks=" + + importingBlocks + + ", requestedBlocks=" + + requestedBlocks + + ", requestedNonAnnouncedBlocks=" + + requestedNonAnnouncedBlocks + + '}'; + } + } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/DefaultSynchronizer.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/DefaultSynchronizer.java index 091feca3d52..684465b0200 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/DefaultSynchronizer.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/DefaultSynchronizer.java @@ -19,6 +19,8 @@ import org.hyperledger.besu.consensus.merge.ForkchoiceMessageListener; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateArchive; +import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.core.Synchronizer; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.sync.checkpointsync.CheckpointDownloaderFactory; @@ -30,7 +32,9 @@ import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapDownloaderFactory; import org.hyperledger.besu.ethereum.eth.sync.state.PendingBlocksManager; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; +import org.hyperledger.besu.ethereum.eth.sync.worldstate.WorldStatePeerTrieNodeFinder; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.worldstate.PeerTrieNodeFinder; import org.hyperledger.besu.ethereum.worldstate.Pruner; import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; import org.hyperledger.besu.metrics.BesuMetricCategory; @@ -57,7 +61,10 @@ public class DefaultSynchronizer implements Synchronizer, ForkchoiceMessageListe private final Optional blockPropagationManager; private final Optional> fastSyncDownloader; private final Optional fullSyncDownloader; + private final EthContext ethContext; private final ProtocolContext protocolContext; + private final WorldStateStorage worldStateStorage; + private final MetricsSystem metricsSystem; private final PivotBlockSelector pivotBlockSelector; private final SyncTerminationCondition terminationCondition; @@ -78,7 +85,10 @@ public DefaultSynchronizer( this.maybePruner = maybePruner; this.syncState = syncState; this.pivotBlockSelector = pivotBlockSelector; + this.ethContext = ethContext; this.protocolContext = protocolContext; + this.worldStateStorage = worldStateStorage; + this.metricsSystem = metricsSystem; this.terminationCondition = terminationCondition; ChainHeadTracker.trackChainHeadForPeers( @@ -193,6 +203,7 @@ public CompletableFuture start() { } else { syncState.markInitialSyncPhaseAsDone(); + enableFallbackNodeFinder(); future = startFullSync(); } return future.thenApply(this::finalizeSync); @@ -241,6 +252,8 @@ private CompletableFuture handleSyncResult(final FastSyncState result) { pivotBlockSelector.close(); syncState.markInitialSyncPhaseAsDone(); + enableFallbackNodeFinder(); + if (terminationCondition.shouldContinueDownload()) { return startFullSync(); } else { @@ -249,6 +262,19 @@ private CompletableFuture handleSyncResult(final FastSyncState result) { } } + private void enableFallbackNodeFinder() { + if (worldStateStorage instanceof BonsaiWorldStateKeyValueStorage) { + final Optional fallbackNodeFinder = + Optional.of( + new WorldStatePeerTrieNodeFinder( + ethContext, protocolContext.getBlockchain(), metricsSystem)); + ((BonsaiWorldStateArchive) protocolContext.getWorldStateArchive()) + .useFallbackNodeFinder(fallbackNodeFinder); + ((BonsaiWorldStateKeyValueStorage) worldStateStorage) + .useFallbackNodeFinder(fallbackNodeFinder); + } + } + private CompletableFuture startFullSync() { maybePruner.ifPresent(Pruner::start); return fullSyncDownloader diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/PipelineChainDownloader.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/PipelineChainDownloader.java index bedf5ea2ceb..6d3abe3d5bc 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/PipelineChainDownloader.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/PipelineChainDownloader.java @@ -119,7 +119,7 @@ private CompletionStage handleFailedDownload(final Throwable error) { pipelineErrorCounter.inc(); if (ExceptionUtils.rootCause(error) instanceof InvalidBlockException) { LOG.warn( - "Invalid block detected. Disconnecting from sync target. {}", + "Invalid block detected (BREACH_OF_PROTOCOL). Disconnecting from sync target. {}", ExceptionUtils.rootCause(error).getMessage()); syncState.disconnectSyncTarget(DisconnectReason.BREACH_OF_PROTOCOL); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/SynchronizerConfiguration.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/SynchronizerConfiguration.java index e18590a774c..4077ec5f6f9 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/SynchronizerConfiguration.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/SynchronizerConfiguration.java @@ -47,6 +47,8 @@ public class SynchronizerConfiguration { public static final int DEFAULT_COMPUTATION_PARALLELISM = 2; public static final int DEFAULT_WORLD_STATE_TASK_CACHE_SIZE = CachingTaskCollection.DEFAULT_CACHE_SIZE; + public static final long DEFAULT_PROPAGATION_MANAGER_GET_BLOCK_TIMEOUT_MILLIS = + TimeUnit.SECONDS.toMillis(60); // Fast sync config private final int fastSyncPivotDistance; @@ -77,6 +79,7 @@ public class SynchronizerConfiguration { private final int computationParallelism; private final int maxTrailingPeers; private final long worldStateMinMillisBeforeStalling; + private final long propagationManagerGetBlockTimeoutMillis; private SynchronizerConfiguration( final int fastSyncPivotDistance, @@ -98,7 +101,8 @@ private SynchronizerConfiguration( final int downloaderParallelism, final int transactionsParallelism, final int computationParallelism, - final int maxTrailingPeers) { + final int maxTrailingPeers, + final long propagationManagerGetBlockTimeoutMillis) { this.fastSyncPivotDistance = fastSyncPivotDistance; this.fastSyncFullValidationRate = fastSyncFullValidationRate; this.fastSyncMinimumPeerCount = fastSyncMinimumPeerCount; @@ -119,6 +123,7 @@ private SynchronizerConfiguration( this.transactionsParallelism = transactionsParallelism; this.computationParallelism = computationParallelism; this.maxTrailingPeers = maxTrailingPeers; + this.propagationManagerGetBlockTimeoutMillis = propagationManagerGetBlockTimeoutMillis; } public static Builder builder() { @@ -234,6 +239,10 @@ public int getMaxTrailingPeers() { return maxTrailingPeers; } + public long getPropagationManagerGetBlockTimeoutMillis() { + return propagationManagerGetBlockTimeoutMillis; + } + public static class Builder { private SyncMode syncMode = SyncMode.FULL; private int fastSyncMinimumPeerCount = DEFAULT_FAST_SYNC_MINIMUM_PEERS; @@ -260,6 +269,9 @@ public static class Builder { private long worldStateMinMillisBeforeStalling = DEFAULT_WORLD_STATE_MIN_MILLIS_BEFORE_STALLING; private int worldStateTaskCacheSize = DEFAULT_WORLD_STATE_TASK_CACHE_SIZE; + private long propagationManagerGetBlockTimeoutMillis = + DEFAULT_PROPAGATION_MANAGER_GET_BLOCK_TIMEOUT_MILLIS; + public Builder fastSyncPivotDistance(final int distance) { fastSyncPivotDistance = distance; return this; @@ -371,6 +383,12 @@ public Builder maxTrailingPeers(final int maxTailingPeers) { return this; } + public Builder propagationManagerGetBlockTimeoutMillis( + final long propagationManagerGetBlockTimeoutMillis) { + this.propagationManagerGetBlockTimeoutMillis = propagationManagerGetBlockTimeoutMillis; + return this; + } + public SynchronizerConfiguration build() { return new SynchronizerConfiguration( fastSyncPivotDistance, @@ -392,7 +410,8 @@ public SynchronizerConfiguration build() { downloaderParallelism, transactionsParallelism, computationParallelism, - maxTrailingPeers); + maxTrailingPeers, + propagationManagerGetBlockTimeoutMillis); } } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncContext.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncContext.java index 25a6a9582b9..78576b105d3 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncContext.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncContext.java @@ -202,24 +202,24 @@ protected void processException(final Throwable throwable) { backwardSyncException -> { if (backwardSyncException.shouldRestart()) { LOG.info( - "Backward sync failed ({}). Current Peers: {}. Retrying in " - + millisBetweenRetries - + " milliseconds...", - backwardSyncException.getMessage(), - ethContext.getEthPeers().peerCount()); - return; + "Backward sync failed ({}). Current Peers: {}. Retrying in {} milliseconds...", + throwable.getMessage(), + ethContext.getEthPeers().peerCount(), + millisBetweenRetries); } else { debugLambda( LOG, "Not recoverable backward sync exception {}", throwable::getMessage); throw backwardSyncException; } }, - () -> - LOG.warn( - "There was an uncaught exception during Backwards Sync. Retrying in " - + millisBetweenRetries - + " milliseconds...", - throwable)); + () -> { + LOG.warn( + "Backward sync failed ({}). Current Peers: {}. Retrying in {} milliseconds...", + throwable.getMessage(), + ethContext.getEthPeers().peerCount(), + millisBetweenRetries); + LOG.debug("Exception details:", throwable); + }); } private Optional extractBackwardSyncException(final Throwable throwable) { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapsyncMetricsManager.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapsyncMetricsManager.java index 99d4f78ccfa..2392a777c76 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapsyncMetricsManager.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapsyncMetricsManager.java @@ -156,7 +156,7 @@ private void print(final boolean isHeal) { public void notifySnapSyncCompleted() { final Duration duration = Duration.ofMillis(System.currentTimeMillis() - startSyncTime); LOG.info( - "Finished snapsync with nodes {} (healed={}) duration {}{}:{},{}", + "Finished worldstate snapsync with nodes {} (healed={}) duration {}{}:{},{}. Sync block import may still be in progress...", nbNodesGenerated.addAndGet(nbNodesHealed.get()), nbNodesHealed, duration.toHoursPart() > 0 ? (duration.toHoursPart() + ":") : "", diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/AccountTrieNodeDataRequest.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/AccountTrieNodeDataRequest.java index 2b5a26ca37a..dff436697e6 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/AccountTrieNodeDataRequest.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/AccountTrieNodeDataRequest.java @@ -65,8 +65,7 @@ protected int doPersist( public Optional getExistingData(final WorldStateStorage worldStateStorage) { return worldStateStorage .getAccountStateTrieNode(getLocation(), getNodeHash()) - .filter(data -> !getLocation().isEmpty()) - .filter(data -> Hash.hash(data).equals(getNodeHash())); + .filter(data -> !getLocation().isEmpty()); } @Override diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/StorageTrieNodeDataRequest.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/StorageTrieNodeDataRequest.java index 70fc02ef530..8d3eedde056 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/StorageTrieNodeDataRequest.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/StorageTrieNodeDataRequest.java @@ -52,9 +52,8 @@ protected int doPersist( @Override public Optional getExistingData(final WorldStateStorage worldStateStorage) { - return worldStateStorage - .getAccountStorageTrieNode(getAccountHash(), getLocation(), getNodeHash()) - .filter(data -> Hash.hash(data).equals(getNodeHash())); + return worldStateStorage.getAccountStorageTrieNode( + getAccountHash(), getLocation(), getNodeHash()); } @Override diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DownloadHeaderSequenceTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DownloadHeaderSequenceTask.java index 2923605ca4f..066e4898593 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DownloadHeaderSequenceTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DownloadHeaderSequenceTask.java @@ -219,10 +219,10 @@ private CompletableFuture> processHeaders( if (error == null && blockPeerTaskResult.getResult() != null) { badBlockManager.addBadBlock(blockPeerTaskResult.getResult()); } - headersResult.getPeer().disconnect(DisconnectReason.BREACH_OF_PROTOCOL); LOG.debug( - "Received invalid headers from peer, disconnecting from: {}", + "Received invalid headers from peer (BREACH_OF_PROTOCOL), disconnecting from: {}", headersResult.getPeer()); + headersResult.getPeer().disconnect(DisconnectReason.BREACH_OF_PROTOCOL); future.completeExceptionally( new InvalidBlockException( "Header failed validation.", diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldStatePeerTrieNodeFinder.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldStatePeerTrieNodeFinder.java new file mode 100644 index 00000000000..97488da8685 --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldStatePeerTrieNodeFinder.java @@ -0,0 +1,156 @@ +/* + * 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.ethereum.eth.sync.worldstate; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.manager.snap.RetryingGetTrieNodeFromPeerTask; +import org.hyperledger.besu.ethereum.eth.manager.task.EthTask; +import org.hyperledger.besu.ethereum.eth.manager.task.RetryingGetNodeDataFromPeerTask; +import org.hyperledger.besu.ethereum.trie.CompactEncoding; +import org.hyperledger.besu.ethereum.worldstate.PeerTrieNodeFinder; +import org.hyperledger.besu.plugin.services.MetricsSystem; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** This class is used to retrieve missing nodes in the trie by querying the peers */ +public class WorldStatePeerTrieNodeFinder implements PeerTrieNodeFinder { + + private static final Logger LOG = LoggerFactory.getLogger(WorldStatePeerTrieNodeFinder.class); + + private final Cache foundNodes = + CacheBuilder.newBuilder().maximumSize(10_000).expireAfterWrite(5, TimeUnit.MINUTES).build(); + + private static final long TIMEOUT_SECONDS = 1; + + final EthContext ethContext; + final Blockchain blockchain; + final MetricsSystem metricsSystem; + + public WorldStatePeerTrieNodeFinder( + final EthContext ethContext, final Blockchain blockchain, final MetricsSystem metricsSystem) { + this.ethContext = ethContext; + this.blockchain = blockchain; + this.metricsSystem = metricsSystem; + } + + @Override + public Optional getAccountStateTrieNode(final Bytes location, final Bytes32 nodeHash) { + Optional cachedValue = Optional.ofNullable(foundNodes.getIfPresent(nodeHash)); + if (cachedValue.isPresent()) { + return cachedValue; + } + final Optional response = + findByGetNodeData(Hash.wrap(nodeHash)) + .or(() -> findByGetTrieNodeData(Hash.wrap(nodeHash), Optional.empty(), location)); + response.ifPresent( + bytes -> { + LOG.debug( + "Fixed missing account state trie node for location {} and hash {}", + location, + nodeHash); + foundNodes.put(nodeHash, bytes); + }); + return response; + } + + @Override + public Optional getAccountStorageTrieNode( + final Hash accountHash, final Bytes location, final Bytes32 nodeHash) { + Optional cachedValue = Optional.ofNullable(foundNodes.getIfPresent(nodeHash)); + if (cachedValue.isPresent()) { + return cachedValue; + } + final Optional response = + findByGetNodeData(Hash.wrap(nodeHash)) + .or( + () -> + findByGetTrieNodeData(Hash.wrap(nodeHash), Optional.of(accountHash), location)); + response.ifPresent( + bytes -> { + LOG.debug( + "Fixed missing storage state trie node for location {} and hash {}", + location, + nodeHash); + foundNodes.put(nodeHash, bytes); + }); + return response; + } + + public Optional findByGetNodeData(final Hash nodeHash) { + final BlockHeader chainHead = blockchain.getChainHeadHeader(); + final RetryingGetNodeDataFromPeerTask retryingGetNodeDataFromPeerTask = + RetryingGetNodeDataFromPeerTask.forHashes( + ethContext, List.of(nodeHash), chainHead.getNumber(), metricsSystem); + try { + final Map response = + retryingGetNodeDataFromPeerTask.run().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + if (response.containsKey(nodeHash)) { + LOG.debug("Found node {} with getNodeData request", nodeHash); + return Optional.of(response.get(nodeHash)); + } else { + LOG.debug("Found invalid node {} with getNodeData request", nodeHash); + } + } catch (InterruptedException | ExecutionException | TimeoutException e) { + LOG.debug("Error when trying to find node {} with getNodeData request", nodeHash); + } + return Optional.empty(); + } + + public Optional findByGetTrieNodeData( + final Hash nodeHash, final Optional accountHash, final Bytes location) { + final BlockHeader chainHead = blockchain.getChainHeadHeader(); + final Map> request = new HashMap<>(); + if (accountHash.isPresent()) { + request.put(accountHash.get(), List.of(CompactEncoding.encode(location))); + } else { + request.put(CompactEncoding.encode(location), new ArrayList<>()); + } + final Bytes path = CompactEncoding.encode(location); + final EthTask> getTrieNodeFromPeerTask = + RetryingGetTrieNodeFromPeerTask.forTrieNodes(ethContext, request, chainHead, metricsSystem); + try { + final Map response = + getTrieNodeFromPeerTask.run().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + final Bytes nodeValue = + response.get(Bytes.concatenate(accountHash.map(Bytes::wrap).orElse(Bytes.EMPTY), path)); + if (nodeValue != null && Hash.hash(nodeValue).equals(nodeHash)) { + LOG.debug("Found node {} with getTrieNode request", nodeHash); + return Optional.of(nodeValue); + } else { + LOG.debug("Found invalid node {} with getTrieNode request", nodeHash); + } + } catch (InterruptedException | ExecutionException | TimeoutException e) { + LOG.debug("Error when trying to find node {} with getTrieNode request", nodeHash); + } + return Optional.empty(); + } +} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageProcessor.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageProcessor.java index e69dbcf8943..e0e00dd4aae 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageProcessor.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageProcessor.java @@ -133,7 +133,9 @@ private void processNewPooledTransactionHashesMessage( } catch (final RLPException ex) { if (peer != null) { LOG.debug( - "Malformed pooled transaction hashes message received, disconnecting: {}", peer, ex); + "Malformed pooled transaction hashes message received (BREACH_OF_PROTOCOL), disconnecting: {}", + peer, + ex); peer.disconnect(DisconnectReason.BREACH_OF_PROTOCOL); } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolConfiguration.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolConfiguration.java index d4e91d45c9d..c8b8e5f6697 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolConfiguration.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolConfiguration.java @@ -26,7 +26,7 @@ public interface TransactionPoolConfiguration { int DEFAULT_TX_MSG_KEEP_ALIVE = 60; int MAX_PENDING_TRANSACTIONS = 4096; - int MAX_PENDING_TRANSACTIONS_HASHES = 4096; + int MAX_FUTURE_TRANSACTION_BY_ACCOUNT = 64; int DEFAULT_TX_RETENTION_HOURS = 13; boolean DEFAULT_STRICT_TX_REPLAY_PROTECTION_ENABLED = false; Percentage DEFAULT_PRICE_BUMP = Percentage.fromInt(10); @@ -40,6 +40,11 @@ default int getTxPoolMaxSize() { return MAX_PENDING_TRANSACTIONS; } + @Value.Default + default int getTxPoolMaxFutureTransactionByAccount() { + return MAX_FUTURE_TRANSACTION_BY_ACCOUNT; + } + @Value.Default default int getPendingTxRetentionPeriod() { return DEFAULT_TX_RETENTION_HOURS; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactory.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactory.java index 876751183bf..02a01f85706 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactory.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactory.java @@ -135,20 +135,16 @@ private static AbstractPendingTransactionsSorter createPendingTransactionsSorter .anyMatch(FeeMarket::implementsBaseFee); if (isFeeMarketImplementBaseFee) { return new BaseFeePendingTransactionsSorter( - transactionPoolConfiguration.getPendingTxRetentionPeriod(), - transactionPoolConfiguration.getTxPoolMaxSize(), + transactionPoolConfiguration, clock, metricsSystem, - protocolContext.getBlockchain()::getChainHeadHeader, - transactionPoolConfiguration.getPriceBump()); + protocolContext.getBlockchain()::getChainHeadHeader); } else { return new GasPricePendingTransactionsSorter( - transactionPoolConfiguration.getPendingTxRetentionPeriod(), - transactionPoolConfiguration.getTxPoolMaxSize(), + transactionPoolConfiguration, clock, metricsSystem, - protocolContext.getBlockchain()::getChainHeadHeader, - transactionPoolConfiguration.getPriceBump()); + protocolContext.getBlockchain()::getChainHeadHeader); } } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionsForSenderInfo.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionsForSenderInfo.java index da34cb714e8..cfca069d84b 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionsForSenderInfo.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionsForSenderInfo.java @@ -18,7 +18,9 @@ import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter; import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionInfo; +import java.util.Map; import java.util.NavigableMap; +import java.util.Optional; import java.util.OptionalLong; import java.util.TreeMap; import java.util.stream.Stream; @@ -80,6 +82,14 @@ public OptionalLong maybeNextNonce() { } } + public Optional maybeLastTx() { + return Optional.ofNullable(transactionsInfos.lastEntry()).map(Map.Entry::getValue); + } + + public int transactionCount() { + return transactionsInfos.size(); + } + public Stream streamTransactionInfos() { return transactionsInfos.values().stream(); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionsMessageProcessor.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionsMessageProcessor.java index 6bd55f58495..f0ba391874a 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionsMessageProcessor.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionsMessageProcessor.java @@ -112,7 +112,10 @@ private void processTransactionsMessage( } catch (final RLPException ex) { if (peer != null) { - LOG.debug("Malformed transaction message received, disconnecting: {}", peer, ex); + LOG.debug( + "Malformed transaction message received (BREACH_OF_PROTOCOL), disconnecting: {}", + peer, + ex); peer.disconnect(DisconnectReason.BREACH_OF_PROTOCOL); } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractPendingTransactionsSorter.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractPendingTransactionsSorter.java index 6f9d0417e16..8a0aad6d5d9 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractPendingTransactionsSorter.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractPendingTransactionsSorter.java @@ -26,6 +26,7 @@ import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactionDroppedListener; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactionListener; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolReplacementHandler; import org.hyperledger.besu.ethereum.eth.transactions.TransactionsForSenderInfo; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; @@ -34,7 +35,6 @@ import org.hyperledger.besu.plugin.services.metrics.Counter; import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; import org.hyperledger.besu.util.Subscribers; -import org.hyperledger.besu.util.number.Percentage; import java.time.Clock; import java.time.Instant; @@ -46,6 +46,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.NavigableSet; import java.util.Optional; import java.util.OptionalLong; import java.util.Set; @@ -68,8 +69,8 @@ public abstract class AbstractPendingTransactionsSorter { private static final Logger LOG = LoggerFactory.getLogger(AbstractPendingTransactionsSorter.class); - protected final int maxTransactionRetentionHours; protected final Clock clock; + protected final TransactionPoolConfiguration poolConfig; protected final Object lock = new Object(); protected final Map pendingTransactions = new ConcurrentHashMap<>(); @@ -87,22 +88,19 @@ public abstract class AbstractPendingTransactionsSorter { protected final Counter localTransactionAddedCounter; protected final Counter remoteTransactionAddedCounter; - protected final long maxPendingTransactions; protected final TransactionPoolReplacementHandler transactionReplacementHandler; protected final Supplier chainHeadHeaderSupplier; public AbstractPendingTransactionsSorter( - final int maxTransactionRetentionHours, - final int maxPendingTransactions, + final TransactionPoolConfiguration poolConfig, final Clock clock, final MetricsSystem metricsSystem, - final Supplier chainHeadHeaderSupplier, - final Percentage priceBump) { - this.maxTransactionRetentionHours = maxTransactionRetentionHours; - this.maxPendingTransactions = maxPendingTransactions; + final Supplier chainHeadHeaderSupplier) { + this.poolConfig = poolConfig; this.clock = clock; this.chainHeadHeaderSupplier = chainHeadHeaderSupplier; - this.transactionReplacementHandler = new TransactionPoolReplacementHandler(priceBump); + this.transactionReplacementHandler = + new TransactionPoolReplacementHandler(poolConfig.getPriceBump()); final LabelledMetric transactionAddedCounter = metricsSystem.createLabelledCounter( BesuMetricCategory.TRANSACTION_POOL, @@ -129,7 +127,7 @@ public AbstractPendingTransactionsSorter( public void evictOldTransactions() { final Instant removeTransactionsBefore = - clock.instant().minus(maxTransactionRetentionHours, ChronoUnit.HOURS); + clock.instant().minus(poolConfig.getPendingTxRetentionPeriod(), ChronoUnit.HOURS); pendingTransactions.values().stream() .filter(transaction -> transaction.getAddedToPoolAt().isBefore(removeTransactionsBefore)) @@ -279,7 +277,7 @@ protected void notifyTransactionDropped(final Transaction transaction) { } public long maxSize() { - return maxPendingTransactions; + return poolConfig.getTxPoolMaxSize(); } public int size() { @@ -424,4 +422,16 @@ public Optional getInvalidReason() { return invalidReason; } } + + Optional lowestValueTxForRemovalBySender(NavigableSet txSet) { + return txSet.descendingSet().stream() + .filter( + tx -> + transactionsBySender + .get(tx.getSender()) + .maybeLastTx() + .filter(tx::equals) + .isPresent()) + .findFirst(); + } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/BaseFeePendingTransactionsSorter.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/BaseFeePendingTransactionsSorter.java index 5d8d2590cd7..2b2d0e8eca0 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/BaseFeePendingTransactionsSorter.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/BaseFeePendingTransactionsSorter.java @@ -23,8 +23,8 @@ import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import org.hyperledger.besu.plugin.services.MetricsSystem; -import org.hyperledger.besu.util.number.Percentage; import java.time.Clock; import java.util.Comparator; @@ -52,19 +52,11 @@ public class BaseFeePendingTransactionsSorter extends AbstractPendingTransaction private Optional baseFee; public BaseFeePendingTransactionsSorter( - final int maxTransactionRetentionHours, - final int maxPendingTransactions, + final TransactionPoolConfiguration poolConfig, final Clock clock, final MetricsSystem metricsSystem, - final Supplier chainHeadHeaderSupplier, - final Percentage priceBump) { - super( - maxTransactionRetentionHours, - maxPendingTransactions, - clock, - metricsSystem, - chainHeadHeaderSupplier, - priceBump); + final Supplier chainHeadHeaderSupplier) { + super(poolConfig, clock, metricsSystem, chainHeadHeaderSupplier); this.baseFee = chainHeadHeaderSupplier.get().getBaseFee(); } @@ -84,7 +76,7 @@ public BaseFeePendingTransactionsSorter( .get() .getAsBigInteger() .longValue()) - .thenComparing(TransactionInfo::getSequence) + .thenComparing(TransactionInfo::getAddedToPoolAt) .reversed()); private final NavigableSet prioritizedTransactionsDynamicRange = @@ -97,7 +89,7 @@ public BaseFeePendingTransactionsSorter( .getMaxFeePerGas() .map(maxFeePerGas -> maxFeePerGas.getAsBigInteger().longValue()) .orElse(transactionInfo.getGasPrice().toLong())) - .thenComparing(TransactionInfo::getSequence) + .thenComparing(TransactionInfo::getAddedToPoolAt) .reversed()); @Override @@ -201,6 +193,7 @@ protected TransactionAddedStatus addTransaction(final TransactionInfo transactio if (!transactionAddedStatus.equals(ADDED)) { return transactionAddedStatus; } + // check if it's in static or dynamic range if (isInStaticRange(transaction, baseFee)) { prioritizedTransactionsStaticRange.add(transactionInfo); @@ -210,25 +203,41 @@ protected TransactionAddedStatus addTransaction(final TransactionInfo transactio LOG.trace("Adding {} to pending transactions", transactionInfo); pendingTransactions.put(transactionInfo.getHash(), transactionInfo); - if (pendingTransactions.size() > maxPendingTransactions) { - final Stream.Builder removalCandidates = Stream.builder(); - if (!prioritizedTransactionsDynamicRange.isEmpty()) - removalCandidates.add(prioritizedTransactionsDynamicRange.last()); - if (!prioritizedTransactionsStaticRange.isEmpty()) - removalCandidates.add(prioritizedTransactionsStaticRange.last()); - final TransactionInfo toRemove = - removalCandidates - .build() - .min( - Comparator.comparing( - txInfo -> txInfo.getTransaction().getEffectivePriorityFeePerGas(baseFee))) - // safe because we just added a tx to the pool so we're guaranteed to have one - .get(); - doRemoveTransaction(toRemove.getTransaction(), false); - LOG.trace("Evicted {} due to transaction pool size", toRemove); - droppedTransaction = Optional.of(toRemove.getTransaction()); + // check if this sender exceeds the transactions by sender limit: + var senderTxInfos = transactionsBySender.get(transactionInfo.getSender()); + if (senderTxInfos.transactionCount() > poolConfig.getTxPoolMaxFutureTransactionByAccount()) { + droppedTransaction = senderTxInfos.maybeLastTx().map(TransactionInfo::getTransaction); + droppedTransaction.ifPresent( + tx -> LOG.trace("Evicted {} due to too many transactions from sender", tx)); + + } else { + // else if we are over txpool limit, select the lowest value transaction to evict + if (pendingTransactions.size() > poolConfig.getTxPoolMaxSize()) { + final Stream.Builder removalCandidates = Stream.builder(); + if (!prioritizedTransactionsDynamicRange.isEmpty()) + lowestValueTxForRemovalBySender(prioritizedTransactionsDynamicRange) + .ifPresent(removalCandidates::add); + if (!prioritizedTransactionsStaticRange.isEmpty()) + lowestValueTxForRemovalBySender(prioritizedTransactionsStaticRange) + .ifPresent(removalCandidates::add); + + droppedTransaction = + removalCandidates + .build() + .min( + Comparator.comparing( + txInfo -> txInfo.getTransaction().getEffectivePriorityFeePerGas(baseFee))) + .map(TransactionInfo::getTransaction); + } } + + droppedTransaction.ifPresent( + toRemove -> { + doRemoveTransaction(toRemove, false); + LOG.trace("Evicted {} due to transaction pool size", toRemove); + }); } + notifyTransactionAdded(transaction); droppedTransaction.ifPresent(this::notifyTransactionDropped); return ADDED; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/GasPricePendingTransactionsSorter.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/GasPricePendingTransactionsSorter.java index db69811446a..bc5d6cb2435 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/GasPricePendingTransactionsSorter.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/GasPricePendingTransactionsSorter.java @@ -19,8 +19,8 @@ import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import org.hyperledger.besu.plugin.services.MetricsSystem; -import org.hyperledger.besu.util.number.Percentage; import java.time.Clock; import java.util.Iterator; @@ -29,6 +29,9 @@ import java.util.TreeSet; import java.util.function.Supplier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * Holds the current set of pending transactions with the ability to iterate them based on priority * for mining or look-up by hash. @@ -36,28 +39,22 @@ *

This class is safe for use across multiple threads. */ public class GasPricePendingTransactionsSorter extends AbstractPendingTransactionsSorter { + private static final Logger LOG = + LoggerFactory.getLogger(GasPricePendingTransactionsSorter.class); private final NavigableSet prioritizedTransactions = new TreeSet<>( comparing(TransactionInfo::isReceivedFromLocalSource) .thenComparing(TransactionInfo::getGasPrice) - .thenComparing(TransactionInfo::getSequence) + .thenComparing(TransactionInfo::getAddedToPoolAt) .reversed()); public GasPricePendingTransactionsSorter( - final int maxTransactionRetentionHours, - final int maxPendingTransactions, + final TransactionPoolConfiguration poolConfig, final Clock clock, final MetricsSystem metricsSystem, - final Supplier chainHeadHeaderSupplier, - final Percentage priceBump) { - super( - maxTransactionRetentionHours, - maxPendingTransactions, - clock, - metricsSystem, - chainHeadHeaderSupplier, - priceBump); + final Supplier chainHeadHeaderSupplier) { + super(poolConfig, clock, metricsSystem, chainHeadHeaderSupplier); } @Override @@ -100,11 +97,21 @@ protected TransactionAddedStatus addTransaction(final TransactionInfo transactio prioritizedTransactions.add(transactionInfo); pendingTransactions.put(transactionInfo.getHash(), transactionInfo); - if (pendingTransactions.size() > maxPendingTransactions) { - final TransactionInfo toRemove = prioritizedTransactions.last(); - doRemoveTransaction(toRemove.getTransaction(), false); - droppedTransaction = Optional.of(toRemove.getTransaction()); + // check if this sender exceeds the transactions by sender limit: + var senderTxInfos = transactionsBySender.get(transactionInfo.getSender()); + if (senderTxInfos.transactionCount() > poolConfig.getTxPoolMaxFutureTransactionByAccount()) { + droppedTransaction = senderTxInfos.maybeLastTx().map(TransactionInfo::getTransaction); + droppedTransaction.ifPresent( + tx -> LOG.trace("Evicted {} due to too many transactions from sender", tx)); + } else { + // else if we are over txpool limit, select the lowest value transaction to evict + if (pendingTransactions.size() > poolConfig.getTxPoolMaxSize()) { + droppedTransaction = + lowestValueTxForRemovalBySender(prioritizedTransactions) + .map(TransactionInfo::getTransaction); + } } + droppedTransaction.ifPresent(tx -> doRemoveTransaction(tx, false)); } notifyTransactionAdded(transactionInfo.getTransaction()); droppedTransaction.ifPresent(this::notifyTransactionDropped); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTest.java index 445c86949e1..aabeae8aa12 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTest.java @@ -73,6 +73,7 @@ import org.hyperledger.besu.testutil.TestClock; import java.math.BigInteger; +import java.time.ZoneId; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -1081,7 +1082,7 @@ public void transactionMessagesGoToTheCorrectExecutor() { protocolSchedule, protocolContext, ethManager.ethContext(), - TestClock.fixed(), + TestClock.system(ZoneId.systemDefault()), metricsSystem, () -> true, new MiningParameters.Builder().minTransactionGasPrice(Wei.ZERO).build(), diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/RequestManagerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/RequestManagerTest.java index b79f75597ac..71cbb2507a3 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/RequestManagerTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/RequestManagerTest.java @@ -23,6 +23,7 @@ import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.RawMessage; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.testutil.TestClock; import java.util.ArrayList; @@ -309,4 +310,29 @@ private EthPeer createPeer() { TestClock.fixed(), Collections.emptyList()); } + + @Test + public void disconnectsPeerOnBadMessage() throws Exception { + for (final boolean supportsRequestId : List.of(true, false)) { + final EthPeer peer = createPeer(); + final RequestManager requestManager = + new RequestManager(peer, supportsRequestId, EthProtocol.NAME); + + requestManager + .dispatchRequest( + messageData -> RLP.input(messageData.getData()).nextSize(), + new RawMessage(0x01, Bytes.EMPTY)) + .then( + (closed, msg, p) -> { + if (!closed) { + RLP.input(msg.getData()).skipNext(); + } + }); + final EthMessage mockMessage = + new EthMessage(peer, new RawMessage(1, Bytes.of(0x81, 0x82, 0x83, 0x84))); + + requestManager.dispatchResponse(mockMessage); + assertThat(peer.isDisconnected()).isTrue(); + } + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/RespondingEthPeer.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/RespondingEthPeer.java index ec091745d89..6da54910c5f 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/RespondingEthPeer.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/RespondingEthPeer.java @@ -29,6 +29,7 @@ import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.eth.EthProtocol; import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; +import org.hyperledger.besu.ethereum.eth.manager.snap.SnapProtocolManager; import org.hyperledger.besu.ethereum.eth.messages.BlockBodiesMessage; import org.hyperledger.besu.ethereum.eth.messages.BlockHeadersMessage; import org.hyperledger.besu.ethereum.eth.messages.EthPV62; @@ -68,14 +69,17 @@ public class RespondingEthPeer { private final EthPeer ethPeer; private final BlockingQueue outgoingMessages; private final EthProtocolManager ethProtocolManager; + private final Optional snapProtocolManager; private final MockPeerConnection peerConnection; private RespondingEthPeer( final EthProtocolManager ethProtocolManager, + final Optional snapProtocolManager, final MockPeerConnection peerConnection, final EthPeer ethPeer, final BlockingQueue outgoingMessages) { this.ethProtocolManager = ethProtocolManager; + this.snapProtocolManager = snapProtocolManager; this.peerConnection = peerConnection; this.ethPeer = ethPeer; this.outgoingMessages = outgoingMessages; @@ -113,6 +117,7 @@ public static Builder builder() { private static RespondingEthPeer create( final EthProtocolManager ethProtocolManager, + final Optional snapProtocolManager, final Hash chainHeadHash, final Difficulty totalDifficulty, final OptionalLong estimatedHeight, @@ -130,7 +135,8 @@ private static RespondingEthPeer create( estimatedHeight.ifPresent(height -> peer.chainState().update(chainHeadHash, height)); peer.registerStatusSent(); - return new RespondingEthPeer(ethProtocolManager, peerConnection, peer, outgoingMessages); + return new RespondingEthPeer( + ethProtocolManager, snapProtocolManager, peerConnection, peer, outgoingMessages); } public EthPeer getEthPeer() { @@ -197,9 +203,16 @@ public boolean respond(final Responder responder) { private void respondToMessage(final Responder responder, final OutgoingMessage msg) { final Optional maybeResponse = responder.respond(msg.capability, msg.messageData); maybeResponse.ifPresent( - (response) -> + (response) -> { + if (ethProtocolManager.getSupportedCapabilities().contains(msg.capability)) { ethProtocolManager.processMessage( - msg.capability, new DefaultMessage(peerConnection, response))); + msg.capability, new DefaultMessage(peerConnection, response)); + } else + snapProtocolManager.ifPresent( + protocolManager -> + protocolManager.processMessage( + msg.capability, new DefaultMessage(peerConnection, response))); + }); } public Optional peekNextOutgoingRequest() { @@ -376,6 +389,7 @@ public static Responder emptyResponder() { public static class Builder { private EthProtocolManager ethProtocolManager; + private Optional snapProtocolManager = Optional.empty(); private Hash chainHeadHash = gen.hash(); private Difficulty totalDifficulty = Difficulty.of(1000L); private OptionalLong estimatedHeight = OptionalLong.of(1000L); @@ -385,7 +399,12 @@ public RespondingEthPeer build() { checkNotNull(ethProtocolManager, "Must configure EthProtocolManager"); return RespondingEthPeer.create( - ethProtocolManager, chainHeadHash, totalDifficulty, estimatedHeight, peerValidators); + ethProtocolManager, + snapProtocolManager, + chainHeadHash, + totalDifficulty, + estimatedHeight, + peerValidators); } public Builder ethProtocolManager(final EthProtocolManager ethProtocolManager) { @@ -394,6 +413,12 @@ public Builder ethProtocolManager(final EthProtocolManager ethProtocolManager) { return this; } + public Builder snapProtocolManager(final SnapProtocolManager snapProtocolManager) { + checkNotNull(snapProtocolManager); + this.snapProtocolManager = Optional.of(snapProtocolManager); + return this; + } + public Builder chainHeadHash(final Hash chainHeadHash) { checkNotNull(chainHeadHash); this.chainHeadHash = chainHeadHash; diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/AbstractMessageTaskTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/AbstractMessageTaskTest.java index d1b787e3013..18b9f3aae4b 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/AbstractMessageTaskTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/AbstractMessageTaskTest.java @@ -44,6 +44,7 @@ import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.testutil.TestClock; +import java.time.ZoneId; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -104,7 +105,7 @@ public void setupTest() { protocolSchedule, protocolContext, ethContext, - TestClock.fixed(), + TestClock.system(ZoneId.systemDefault()), metricsSystem, syncState::isInitialSyncPhaseDone, new MiningParameters.Builder().minTransactionGasPrice(Wei.ONE).build(), diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/SnapProtocolManagerTestUtil.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/SnapProtocolManagerTestUtil.java new file mode 100644 index 00000000000..68bd4e4e5a1 --- /dev/null +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/SnapProtocolManagerTestUtil.java @@ -0,0 +1,61 @@ +/* + * 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.ethereum.eth.manager.task; + +import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; +import org.hyperledger.besu.ethereum.eth.manager.EthMessages; +import org.hyperledger.besu.ethereum.eth.manager.EthPeers; +import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; +import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer; +import org.hyperledger.besu.ethereum.eth.manager.snap.SnapProtocolManager; +import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; +import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; + +import java.util.Collections; + +public class SnapProtocolManagerTestUtil { + + public static SnapProtocolManager create(final EthPeers ethPeers) { + return create( + BlockchainSetupUtil.forTesting(DataStorageFormat.FOREST).getWorldArchive(), ethPeers); + } + + public static SnapProtocolManager create( + final WorldStateArchive worldStateArchive, final EthPeers ethPeers) { + + EthMessages messages = new EthMessages(); + + return new SnapProtocolManager(Collections.emptyList(), ethPeers, messages, worldStateArchive); + } + + public static SnapProtocolManager create( + final WorldStateArchive worldStateArchive, + final EthPeers ethPeers, + final EthMessages snapMessages) { + return new SnapProtocolManager( + Collections.emptyList(), ethPeers, snapMessages, worldStateArchive); + } + + public static RespondingEthPeer createPeer( + final EthProtocolManager ethProtocolManager, + final SnapProtocolManager snapProtocolManager, + final long estimatedHeight) { + return RespondingEthPeer.builder() + .ethProtocolManager(ethProtocolManager) + .snapProtocolManager(snapProtocolManager) + .estimatedHeight(estimatedHeight) + .build(); + } +} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/AbstractBlockPropagationManagerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/AbstractBlockPropagationManagerTest.java index 3f15a68cd99..39666aebb54 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/AbstractBlockPropagationManagerTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/AbstractBlockPropagationManagerTest.java @@ -50,6 +50,7 @@ import org.hyperledger.besu.ethereum.eth.messages.EthPV62; import org.hyperledger.besu.ethereum.eth.messages.NewBlockHashesMessage; import org.hyperledger.besu.ethereum.eth.messages.NewBlockMessage; +import org.hyperledger.besu.ethereum.eth.sync.BlockPropagationManager.ProcessingBlocksManager; import org.hyperledger.besu.ethereum.eth.sync.state.PendingBlocksManager; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; @@ -87,6 +88,8 @@ public abstract class AbstractBlockPropagationManagerTest { spy( new PendingBlocksManager( SynchronizerConfiguration.builder().blockPropagationRange(-10, 30).build())); + protected final ProcessingBlocksManager processingBlocksManager = + spy(new ProcessingBlocksManager()); protected SyncState syncState; protected final MetricsSystem metricsSystem = new NoOpMetricsSystem(); private final Hash finalizedHash = Hash.fromHexStringLenient("0x1337"); @@ -119,7 +122,8 @@ protected void setup(final DataStorageFormat dataStorageFormat) { syncState, pendingBlocksManager, metricsSystem, - blockBroadcaster); + blockBroadcaster, + processingBlocksManager); } @Test @@ -933,7 +937,7 @@ public void shouldNotListenToBlockAddedEventsWhenTTDReachedAndFinal() { } @Test - public void shouldRequestBlockAgainIfFirstGetBlockFails() { + public void shouldRequestBlockFromOtherPeersIfFirstPeerFails() { blockchainUtil.importFirstBlocks(2); final Block nextBlock = blockchainUtil.getBlock(2); @@ -956,15 +960,18 @@ public void shouldRequestBlockAgainIfFirstGetBlockFails() { assertThat(blockchain.contains(nextBlock.getHash())).isFalse(); - // Re-broadcast the previous message and peer responds + // second peer responds final RespondingEthPeer secondPeer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 0); - EthProtocolManagerTestUtil.broadcastMessage(ethProtocolManager, secondPeer, nextAnnouncement); final Responder goodResponder = RespondingEthPeer.blockchainResponder(getFullBlockchain()); secondPeer.respondWhile(goodResponder, secondPeer::hasOutstandingRequests); assertThat(blockchain.contains(nextBlock.getHash())).isTrue(); + verify(processingBlocksManager).addRequestedBlock(nextBlock.getHash()); + verify(processingBlocksManager).addImportingBlock(nextBlock.getHash()); + verify(processingBlocksManager).registerReceivedBlock(nextBlock); + verify(processingBlocksManager).registerBlockImportDone(nextBlock.getHash()); } public abstract Blockchain getFullBlockchain(); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/CompleteBlocksTaskTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/CompleteBlocksTaskTest.java index 2619313fcba..5193dd99865 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/CompleteBlocksTaskTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/CompleteBlocksTaskTest.java @@ -85,21 +85,29 @@ public void shouldReduceTheBlockSegmentSizeAfterEachRetry() { final RespondingEthPeer respondingPeer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); - peerCountToTimeout.set(3); final List requestedData = generateDataToBeRequested(10); final CompleteBlocksTask task = createTask(requestedData); final CompletableFuture> future = task.run(); - final List messageCollector = new ArrayList<>(); + final List messageCollector = new ArrayList<>(4); - respondingPeer.respond( + peerCountToTimeout.set(4); + // after 3 timeouts a peer is disconnected, so we need another peer to reach 4 retries + respondingPeer.respondTimes( + RespondingEthPeer.wrapResponderWithCollector( + RespondingEthPeer.emptyResponder(), messageCollector), + 3); + final RespondingEthPeer respondingPeer2 = + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + respondingPeer2.respond( RespondingEthPeer.wrapResponderWithCollector( RespondingEthPeer.emptyResponder(), messageCollector)); assertThat(batchSize(messageCollector.get(0))).isEqualTo(10); assertThat(batchSize(messageCollector.get(1))).isEqualTo(5); assertThat(batchSize(messageCollector.get(2))).isEqualTo(4); + assertThat(batchSize(messageCollector.get(3))).isEqualTo(3); assertThat(future.isCompletedExceptionally()).isTrue(); assertThatThrownBy(future::get).hasCauseInstanceOf(MaxRetriesReachedException.class); } @@ -110,21 +118,29 @@ public void shouldNotReduceTheBlockSegmentSizeIfOnlyOneBlockNeeded() { final RespondingEthPeer respondingPeer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); - peerCountToTimeout.set(3); final List requestedData = generateDataToBeRequested(1); final EthTask> task = createTask(requestedData); final CompletableFuture> future = task.run(); - final List messageCollector = new ArrayList<>(); + final List messageCollector = new ArrayList<>(4); - respondingPeer.respond( + peerCountToTimeout.set(4); + // after 3 timeouts a peer is disconnected, so we need another peer to reach 4 retries + respondingPeer.respondTimes( + RespondingEthPeer.wrapResponderWithCollector( + RespondingEthPeer.emptyResponder(), messageCollector), + 3); + final RespondingEthPeer respondingPeer2 = + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + respondingPeer2.respond( RespondingEthPeer.wrapResponderWithCollector( RespondingEthPeer.emptyResponder(), messageCollector)); assertThat(batchSize(messageCollector.get(0))).isEqualTo(1); assertThat(batchSize(messageCollector.get(1))).isEqualTo(1); assertThat(batchSize(messageCollector.get(2))).isEqualTo(1); + assertThat(batchSize(messageCollector.get(3))).isEqualTo(1); assertThat(future.isCompletedExceptionally()).isTrue(); assertThatThrownBy(future::get).hasCauseInstanceOf(MaxRetriesReachedException.class); } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DetermineCommonAncestorTaskTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DetermineCommonAncestorTaskTest.java index 1312a94711c..f8523b3c557 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DetermineCommonAncestorTaskTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DetermineCommonAncestorTaskTest.java @@ -42,6 +42,7 @@ import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManagerTestUtil; import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer; import org.hyperledger.besu.ethereum.eth.manager.exceptions.EthTaskException; +import org.hyperledger.besu.ethereum.eth.manager.exceptions.EthTaskException.FailureReason; import org.hyperledger.besu.ethereum.eth.manager.task.EthTask; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; @@ -117,8 +118,7 @@ public void shouldFailIfPeerDisconnects() { assertThat(failure.get()).isNotNull(); final Throwable error = ExceptionUtils.rootCause(failure.get()); assertThat(error).isInstanceOf(EthTaskException.class); - assertThat(((EthTaskException) error).reason()) - .isEqualTo(EthTaskException.FailureReason.PEER_DISCONNECTED); + assertThat(((EthTaskException) error).reason()).isEqualTo(FailureReason.NO_AVAILABLE_PEERS); } @Test diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldStatePeerTrieNodeFinderTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldStatePeerTrieNodeFinderTest.java new file mode 100644 index 00000000000..12cc11b324a --- /dev/null +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldStatePeerTrieNodeFinderTest.java @@ -0,0 +1,261 @@ +/* + * 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.ethereum.eth.sync.worldstate; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.ethereum.eth.manager.EthPeers; +import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; +import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManagerTestUtil; +import org.hyperledger.besu.ethereum.eth.manager.PeerRequest; +import org.hyperledger.besu.ethereum.eth.manager.RequestManager; +import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer; +import org.hyperledger.besu.ethereum.eth.manager.snap.SnapProtocolManager; +import org.hyperledger.besu.ethereum.eth.manager.task.SnapProtocolManagerTestUtil; +import org.hyperledger.besu.ethereum.eth.messages.EthPV63; +import org.hyperledger.besu.ethereum.eth.messages.NodeDataMessage; +import org.hyperledger.besu.ethereum.eth.messages.snap.SnapV1; +import org.hyperledger.besu.ethereum.eth.messages.snap.TrieNodesMessage; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; + +import java.math.BigInteger; +import java.util.List; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.assertj.core.api.Assertions; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@SuppressWarnings({"FieldCanBeLocal", "unused"}) +@RunWith(MockitoJUnitRunner.class) +public class WorldStatePeerTrieNodeFinderTest { + + WorldStatePeerTrieNodeFinder worldStatePeerTrieNodeFinder; + + private final BlockHeaderTestFixture blockHeaderBuilder = new BlockHeaderTestFixture(); + + @Mock private Blockchain blockchain; + private EthProtocolManager ethProtocolManager; + private SnapProtocolManager snapProtocolManager; + private EthPeers ethPeers; + private final PeerRequest peerRequest = mock(PeerRequest.class); + private final RequestManager.ResponseStream responseStream = + mock(RequestManager.ResponseStream.class); + + @Before + public void setup() throws Exception { + ethProtocolManager = EthProtocolManagerTestUtil.create(); + ethPeers = ethProtocolManager.ethContext().getEthPeers(); + snapProtocolManager = SnapProtocolManagerTestUtil.create(ethPeers); + worldStatePeerTrieNodeFinder = + new WorldStatePeerTrieNodeFinder( + ethProtocolManager.ethContext(), blockchain, new NoOpMetricsSystem()); + } + + private RespondingEthPeer.Responder respondToGetNodeDataRequest( + final RespondingEthPeer peer, final Bytes32 nodeValue) { + return RespondingEthPeer.targetedResponder( + (cap, msg) -> { + if (msg.getCode() != EthPV63.GET_NODE_DATA) { + return false; + } + return true; + }, + (cap, msg) -> NodeDataMessage.create(List.of(nodeValue))); + } + + private RespondingEthPeer.Responder respondToGetTrieNodeRequest( + final RespondingEthPeer peer, final Bytes32 nodeValue) { + return RespondingEthPeer.targetedResponder( + (cap, msg) -> { + if (msg.getCode() != SnapV1.GET_TRIE_NODES) { + return false; + } + return true; + }, + (cap, msg) -> TrieNodesMessage.create(Optional.of(BigInteger.ZERO), List.of(nodeValue))); + } + + @Test + public void getAccountStateTrieNodeShouldReturnValueFromGetNodeDataRequest() { + + BlockHeader blockHeader = blockHeaderBuilder.number(1000).buildHeader(); + when(blockchain.getChainHeadHeader()).thenReturn(blockHeader); + + final Bytes32 nodeValue = Bytes32.random(); + final Bytes32 nodeHash = Hash.hash(nodeValue); + + final RespondingEthPeer targetPeer = + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, blockHeader.getNumber()); + + var response = + new Object() { + Optional accountStateTrieNode = Optional.empty(); + }; + + new Thread( + () -> + targetPeer.respondWhileOtherThreadsWork( + respondToGetNodeDataRequest(targetPeer, nodeValue), + () -> response.accountStateTrieNode.isEmpty())) + .start(); + + response.accountStateTrieNode = + worldStatePeerTrieNodeFinder.getAccountStateTrieNode(Bytes.EMPTY, nodeHash); + + Assertions.assertThat(response.accountStateTrieNode).contains(nodeValue); + } + + @Test + public void getAccountStateTrieNodeShouldReturnValueFromGetTrieNodeRequest() { + + final BlockHeader blockHeader = blockHeaderBuilder.number(1000).buildHeader(); + when(blockchain.getChainHeadHeader()).thenReturn(blockHeader); + + final Bytes32 nodeValue = Bytes32.random(); + final Bytes32 nodeHash = Hash.hash(nodeValue); + + final RespondingEthPeer targetPeer = + SnapProtocolManagerTestUtil.createPeer( + ethProtocolManager, snapProtocolManager, blockHeader.getNumber()); + + var response = + new Object() { + Optional accountStateTrieNode = Optional.empty(); + }; + + new Thread( + () -> + targetPeer.respondWhileOtherThreadsWork( + respondToGetTrieNodeRequest(targetPeer, nodeValue), + () -> response.accountStateTrieNode.isEmpty())) + .start(); + + response.accountStateTrieNode = + worldStatePeerTrieNodeFinder.getAccountStateTrieNode(Bytes.EMPTY, nodeHash); + Assertions.assertThat(response.accountStateTrieNode).contains(nodeValue); + } + + @Test + public void getAccountStateTrieNodeShouldReturnEmptyWhenFoundNothing() { + + final BlockHeader blockHeader = blockHeaderBuilder.number(1000).buildHeader(); + when(blockchain.getChainHeadHeader()).thenReturn(blockHeader); + + final Bytes32 nodeValue = Bytes32.random(); + final Bytes32 nodeHash = Hash.hash(nodeValue); + + var response = + new Object() { + Optional accountStateTrieNode = Optional.empty(); + }; + + response.accountStateTrieNode = + worldStatePeerTrieNodeFinder.getAccountStateTrieNode(Bytes.EMPTY, nodeHash); + Assertions.assertThat(response.accountStateTrieNode).isEmpty(); + } + + @Test + public void getAccountStorageTrieNodeShouldReturnValueFromGetNodeDataRequest() { + + BlockHeader blockHeader = blockHeaderBuilder.number(1000).buildHeader(); + when(blockchain.getChainHeadHeader()).thenReturn(blockHeader); + + final Hash accountHash = Hash.wrap(Bytes32.random()); + final Bytes32 nodeValue = Bytes32.random(); + final Bytes32 nodeHash = Hash.hash(nodeValue); + + final RespondingEthPeer targetPeer = + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, blockHeader.getNumber()); + + var response = + new Object() { + Optional accountStateTrieNode = Optional.empty(); + }; + + new Thread( + () -> + targetPeer.respondWhileOtherThreadsWork( + respondToGetNodeDataRequest(targetPeer, nodeValue), + () -> response.accountStateTrieNode.isEmpty())) + .start(); + + response.accountStateTrieNode = + worldStatePeerTrieNodeFinder.getAccountStorageTrieNode(accountHash, Bytes.EMPTY, nodeHash); + + Assertions.assertThat(response.accountStateTrieNode).contains(nodeValue); + } + + @Test + public void getAccountStorageTrieNodeShouldReturnValueFromGetTrieNodeRequest() { + + final BlockHeader blockHeader = blockHeaderBuilder.number(1000).buildHeader(); + when(blockchain.getChainHeadHeader()).thenReturn(blockHeader); + + final Hash accountHash = Hash.wrap(Bytes32.random()); + final Bytes32 nodeValue = Bytes32.random(); + final Bytes32 nodeHash = Hash.hash(nodeValue); + + final RespondingEthPeer targetPeer = + SnapProtocolManagerTestUtil.createPeer( + ethProtocolManager, snapProtocolManager, blockHeader.getNumber()); + + var response = + new Object() { + Optional accountStateTrieNode = Optional.empty(); + }; + + new Thread( + () -> + targetPeer.respondWhileOtherThreadsWork( + respondToGetTrieNodeRequest(targetPeer, nodeValue), + () -> response.accountStateTrieNode.isEmpty())) + .start(); + + response.accountStateTrieNode = + worldStatePeerTrieNodeFinder.getAccountStorageTrieNode(accountHash, Bytes.EMPTY, nodeHash); + Assertions.assertThat(response.accountStateTrieNode).contains(nodeValue); + } + + @Test + public void getAccountStorageTrieNodeShouldReturnEmptyWhenFoundNothing() { + + final BlockHeader blockHeader = blockHeaderBuilder.number(1000).buildHeader(); + when(blockchain.getChainHeadHeader()).thenReturn(blockHeader); + + final Hash accountHash = Hash.wrap(Bytes32.random()); + final Bytes32 nodeValue = Bytes32.random(); + final Bytes32 nodeHash = Hash.hash(nodeValue); + + var response = + new Object() { + Optional accountStateTrieNode = Optional.empty(); + }; + + response.accountStateTrieNode = + worldStatePeerTrieNodeFinder.getAccountStorageTrieNode(accountHash, Bytes.EMPTY, nodeHash); + Assertions.assertThat(response.accountStateTrieNode).isEmpty(); + } +} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/BaseFeePendingTransactionsTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/BaseFeePendingTransactionsTest.java index 55ba5fd97e7..a14dab7d6d2 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/BaseFeePendingTransactionsTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/BaseFeePendingTransactionsTest.java @@ -31,12 +31,14 @@ import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionTestFixture; import org.hyperledger.besu.ethereum.core.Util; +import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter; import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionAddedStatus; import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionSelectionResult; import org.hyperledger.besu.ethereum.eth.transactions.sorter.BaseFeePendingTransactionsSorter; import org.hyperledger.besu.metrics.StubMetricsSystem; import org.hyperledger.besu.testutil.TestClock; +import java.time.ZoneId; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; @@ -53,6 +55,7 @@ public class BaseFeePendingTransactionsTest { private static final int MAX_TRANSACTIONS = 5; + private static final int MAX_TRANSACTIONS_BY_SENDER = 4; private static final Supplier SIGNATURE_ALGORITHM = Suppliers.memoize(SignatureAlgorithmFactory::getInstance); private static final KeyPair KEYS1 = SIGNATURE_ALGORITHM.get().generateKeyPair(); @@ -67,12 +70,19 @@ public class BaseFeePendingTransactionsTest { private final StubMetricsSystem metricsSystem = new StubMetricsSystem(); private final BaseFeePendingTransactionsSorter transactions = new BaseFeePendingTransactionsSorter( - TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, - MAX_TRANSACTIONS, - TestClock.fixed(), + ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(MAX_TRANSACTIONS).build(), + TestClock.system(ZoneId.systemDefault()), metricsSystem, - BaseFeePendingTransactionsTest::mockBlockHeader, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP); + BaseFeePendingTransactionsTest::mockBlockHeader); + private final BaseFeePendingTransactionsSorter senderLimitedTransactions = + new BaseFeePendingTransactionsSorter( + ImmutableTransactionPoolConfiguration.builder() + .txPoolMaxSize(MAX_TRANSACTIONS) + .txPoolMaxFutureTransactionByAccount(MAX_TRANSACTIONS_BY_SENDER) + .build(), + TestClock.system(ZoneId.systemDefault()), + metricsSystem, + BaseFeePendingTransactionsTest::mockBlockHeader); private final Transaction transaction1 = createTransaction(2); private final Transaction transaction2 = createTransaction(1); @@ -122,20 +132,36 @@ public void shouldGetTransactionByHash() { @Test public void shouldDropOldestTransactionWhenLimitExceeded() { - final Transaction oldestTransaction = createTransaction(0); - transactions.addRemoteTransaction(oldestTransaction); + final Transaction oldestTransaction = + transactionWithNonceSenderAndGasPrice(0, SIGNATURE_ALGORITHM.get().generateKeyPair(), 10L); + senderLimitedTransactions.addRemoteTransaction(oldestTransaction); for (int i = 1; i < MAX_TRANSACTIONS; i++) { - transactions.addRemoteTransaction(createTransaction(i)); + senderLimitedTransactions.addRemoteTransaction( + transactionWithNonceSenderAndGasPrice( + i, SIGNATURE_ALGORITHM.get().generateKeyPair(), 10L)); } - assertThat(transactions.size()).isEqualTo(MAX_TRANSACTIONS); + assertThat(senderLimitedTransactions.size()).isEqualTo(MAX_TRANSACTIONS); assertThat(metricsSystem.getCounterValue(REMOVED_COUNTER, REMOTE, DROPPED)).isZero(); - transactions.addRemoteTransaction(createTransaction(MAX_TRANSACTIONS + 1)); - assertThat(transactions.size()).isEqualTo(MAX_TRANSACTIONS); + senderLimitedTransactions.addRemoteTransaction(createTransaction(MAX_TRANSACTIONS + 1)); + assertThat(senderLimitedTransactions.size()).isEqualTo(MAX_TRANSACTIONS); assertTransactionNotPending(oldestTransaction); assertThat(metricsSystem.getCounterValue(REMOVED_COUNTER, REMOTE, DROPPED)).isEqualTo(1); } + @Test + public void shouldDropFutureTransactionWhenSenderLimitExceeded() { + Transaction furthestFutureTransaction = null; + for (int i = 0; i < MAX_TRANSACTIONS; i++) { + furthestFutureTransaction = transactionWithNonceSenderAndGasPrice(i, KEYS1, 10L); + senderLimitedTransactions.addRemoteTransaction(furthestFutureTransaction); + } + assertThat(senderLimitedTransactions.size()).isEqualTo(MAX_TRANSACTIONS_BY_SENDER); + assertThat(metricsSystem.getCounterValue(REMOVED_COUNTER, REMOTE, DROPPED)).isEqualTo(1L); + assertThat(senderLimitedTransactions.getTransactionByHash(furthestFutureTransaction.getHash())) + .isEmpty(); + } + @Test public void shouldHandleMaximumTransactionLimitCorrectlyWhenSameTransactionAddedMultipleTimes() { transactions.addRemoteTransaction(createTransaction(0)); @@ -167,10 +193,13 @@ public void shouldPrioritizeLocalTransaction() { public void shouldPrioritizeGasPriceThenTimeAddedToPool() { final List lowGasPriceTransactions = IntStream.range(0, MAX_TRANSACTIONS) - .mapToObj(i -> transactionWithNonceSenderAndGasPrice(i + 1, KEYS1, 10)) + .mapToObj( + i -> + transactionWithNonceSenderAndGasPrice( + i + 1, SIGNATURE_ALGORITHM.get().generateKeyPair(), 10)) .collect(Collectors.toUnmodifiableList()); - // Fill the pool + // Fill the pool with transasctions from random senders lowGasPriceTransactions.forEach(transactions::addRemoteTransaction); // This should kick the oldest tx with the low gas price out, namely the first one we added @@ -186,14 +215,14 @@ public void shouldPrioritizeGasPriceThenTimeAddedToPool() { @Test public void shouldStartDroppingLocalTransactionsWhenPoolIsFullOfLocalTransactions() { - final Transaction firstLocalTransaction = createTransaction(0); - transactions.addLocalTransaction(firstLocalTransaction); + Transaction lastLocalTransactionForSender = null; - for (int i = 1; i <= MAX_TRANSACTIONS; i++) { - transactions.addLocalTransaction(createTransaction(i)); + for (int i = 0; i <= MAX_TRANSACTIONS; i++) { + lastLocalTransactionForSender = createTransaction(i); + transactions.addLocalTransaction(lastLocalTransactionForSender); } assertThat(transactions.size()).isEqualTo(MAX_TRANSACTIONS); - assertTransactionNotPending(firstLocalTransaction); + assertTransactionNotPending(lastLocalTransactionForSender); } @Test @@ -590,12 +619,13 @@ public void shouldEvictMultipleOldTransactions() { final int maxTransactionRetentionHours = 1; final BaseFeePendingTransactionsSorter transactions = new BaseFeePendingTransactionsSorter( - maxTransactionRetentionHours, - MAX_TRANSACTIONS, + ImmutableTransactionPoolConfiguration.builder() + .pendingTxRetentionPeriod(maxTransactionRetentionHours) + .txPoolMaxSize(MAX_TRANSACTIONS) + .build(), clock, metricsSystem, - BaseFeePendingTransactionsTest::mockBlockHeader, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP); + BaseFeePendingTransactionsTest::mockBlockHeader); transactions.addRemoteTransaction(transaction1); assertThat(transactions.size()).isEqualTo(1); @@ -613,12 +643,13 @@ public void shouldEvictSingleOldTransaction() { final int maxTransactionRetentionHours = 1; final BaseFeePendingTransactionsSorter transactions = new BaseFeePendingTransactionsSorter( - maxTransactionRetentionHours, - MAX_TRANSACTIONS, + ImmutableTransactionPoolConfiguration.builder() + .pendingTxRetentionPeriod(maxTransactionRetentionHours) + .txPoolMaxSize(MAX_TRANSACTIONS) + .build(), clock, metricsSystem, - BaseFeePendingTransactionsTest::mockBlockHeader, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP); + BaseFeePendingTransactionsTest::mockBlockHeader); transactions.addRemoteTransaction(transaction1); assertThat(transactions.size()).isEqualTo(1); clock.step(2L, ChronoUnit.HOURS); @@ -632,12 +663,13 @@ public void shouldEvictExclusivelyOldTransactions() { final int maxTransactionRetentionHours = 2; final BaseFeePendingTransactionsSorter transactions = new BaseFeePendingTransactionsSorter( - maxTransactionRetentionHours, - MAX_TRANSACTIONS, + ImmutableTransactionPoolConfiguration.builder() + .pendingTxRetentionPeriod(maxTransactionRetentionHours) + .txPoolMaxSize(MAX_TRANSACTIONS) + .build(), clock, metricsSystem, - BaseFeePendingTransactionsTest::mockBlockHeader, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP); + BaseFeePendingTransactionsTest::mockBlockHeader); transactions.addRemoteTransaction(transaction1); assertThat(transactions.size()).isEqualTo(1); clock.step(3L, ChronoUnit.HOURS); @@ -687,9 +719,32 @@ public void assertThatCorrectNonceIsReturned() { .isPresent() .hasValue(6); addLocalTransactions(6, 10); + + // assert that we drop future nonces first: assertThat(transactions.getNextNonceForSender(transaction1.getSender())) .isPresent() - .hasValue(7); + .hasValue(6); + } + + @Test + public void assertThatCorrectNonceIsReturnedForSenderLimitedPool() { + assertThat(senderLimitedTransactions.getNextNonceForSender(transaction1.getSender())).isEmpty(); + addLocalTransactions(senderLimitedTransactions, 1, 2, 4, 5); + assertThat(senderLimitedTransactions.getNextNonceForSender(transaction1.getSender())) + .isPresent() + .hasValue(3); + addLocalTransactions(senderLimitedTransactions, 3); + + // assert we have dropped previously added tx 5, and next nonce is now 5 + assertThat(senderLimitedTransactions.getNextNonceForSender(transaction1.getSender())) + .isPresent() + .hasValue(5); + addLocalTransactions(senderLimitedTransactions, 6, 10); + + // assert that we drop future nonces first: + assertThat(senderLimitedTransactions.getNextNonceForSender(transaction1.getSender())) + .isPresent() + .hasValue(5); } @Test @@ -713,8 +768,13 @@ public void assertThatCorrectNonceIsReturnedWithRepeatedTXes() { } private void addLocalTransactions(final long... nonces) { + addLocalTransactions(transactions, nonces); + } + + private void addLocalTransactions( + final AbstractPendingTransactionsSorter sorter, final long... nonces) { for (final long nonce : nonces) { - transactions.addLocalTransaction(createTransaction(nonce)); + sorter.addLocalTransaction(createTransaction(nonce)); } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/GasPricePendingTransactionsTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/GasPricePendingTransactionsTest.java index c4d9ab73d98..5b47ad7aee1 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/GasPricePendingTransactionsTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/GasPricePendingTransactionsTest.java @@ -37,6 +37,7 @@ import org.hyperledger.besu.metrics.StubMetricsSystem; import org.hyperledger.besu.testutil.TestClock; +import java.time.ZoneId; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; @@ -53,6 +54,7 @@ public class GasPricePendingTransactionsTest { private static final int MAX_TRANSACTIONS = 5; + private static final int MAX_TRANSACTIONS_BY_SENDER = 4; private static final Supplier SIGNATURE_ALGORITHM = Suppliers.memoize(SignatureAlgorithmFactory::getInstance); private static final KeyPair KEYS1 = SIGNATURE_ALGORITHM.get().generateKeyPair(); @@ -67,12 +69,10 @@ public class GasPricePendingTransactionsTest { private final StubMetricsSystem metricsSystem = new StubMetricsSystem(); private final GasPricePendingTransactionsSorter transactions = new GasPricePendingTransactionsSorter( - TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, - MAX_TRANSACTIONS, - TestClock.fixed(), + ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(MAX_TRANSACTIONS).build(), + TestClock.system(ZoneId.systemDefault()), metricsSystem, - GasPricePendingTransactionsTest::mockBlockHeader, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP); + GasPricePendingTransactionsTest::mockBlockHeader); private final Transaction transaction1 = createTransaction(2); private final Transaction transaction2 = createTransaction(1); @@ -151,6 +151,30 @@ public void shouldDropOldestTransactionWhenLimitExceeded() { assertThat(metricsSystem.getCounterValue(REMOVED_COUNTER, REMOTE, DROPPED)).isEqualTo(1); } + @Test + public void shouldDropFutureTransactionWhenSenderLimitExceeded() { + final GasPricePendingTransactionsSorter senderLimitedtransactions = + new GasPricePendingTransactionsSorter( + ImmutableTransactionPoolConfiguration.builder() + .txPoolMaxSize(MAX_TRANSACTIONS) + .txPoolMaxFutureTransactionByAccount(MAX_TRANSACTIONS_BY_SENDER) + .build(), + TestClock.system(ZoneId.systemDefault()), + metricsSystem, + GasPricePendingTransactionsTest::mockBlockHeader); + + Transaction furthestFutureTransaction = null; + for (int i = 0; i < MAX_TRANSACTIONS; i++) { + furthestFutureTransaction = transactionWithNonceSenderAndGasPrice(i, KEYS1, 10L); + senderLimitedtransactions.addRemoteTransaction(furthestFutureTransaction); + } + assertThat(senderLimitedtransactions.size()).isEqualTo(MAX_TRANSACTIONS_BY_SENDER); + assertThat(metricsSystem.getCounterValue(REMOVED_COUNTER, REMOTE, DROPPED)).isEqualTo(1L); + + assertThat(senderLimitedtransactions.getTransactionByHash(furthestFutureTransaction.getHash())) + .isEmpty(); + } + @Test public void shouldHandleMaximumTransactionLimitCorrectlyWhenSameTransactionAddedMultipleTimes() { transactions.addRemoteTransaction(createTransaction(0)); @@ -207,16 +231,14 @@ public void shouldPrioritizeGasPriceThenTimeAddedToPool() { @Test public void shouldStartDroppingLocalTransactionsWhenPoolIsFullOfLocalTransactions() { + Transaction lastLocalTransactionForSender = null; - final Transaction firstLocalTransaction = createTransaction(0); - transactions.addLocalTransaction(firstLocalTransaction); - - for (int i = 1; i <= MAX_TRANSACTIONS; i++) { - transactions.addLocalTransaction(createTransaction(i)); + for (int i = 0; i <= MAX_TRANSACTIONS; i++) { + lastLocalTransactionForSender = createTransaction(i); + transactions.addLocalTransaction(lastLocalTransactionForSender); } - assertThat(transactions.size()).isEqualTo(MAX_TRANSACTIONS); - assertTransactionNotPending(firstLocalTransaction); + assertTransactionNotPending(lastLocalTransactionForSender); } @Test @@ -610,15 +632,15 @@ private Transaction createTransaction(final long transactionNumber) { @Test public void shouldEvictMultipleOldTransactions() { - final int maxTransactionRetentionHours = 1; final GasPricePendingTransactionsSorter transactions = new GasPricePendingTransactionsSorter( - maxTransactionRetentionHours, - MAX_TRANSACTIONS, + ImmutableTransactionPoolConfiguration.builder() + .pendingTxRetentionPeriod(1) + .txPoolMaxSize(MAX_TRANSACTIONS) + .build(), clock, metricsSystem, - () -> null, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP); + () -> null); transactions.addRemoteTransaction(transaction1); assertThat(transactions.size()).isEqualTo(1); @@ -633,15 +655,15 @@ public void shouldEvictMultipleOldTransactions() { @Test public void shouldEvictSingleOldTransaction() { - final int maxTransactionRetentionHours = 1; final GasPricePendingTransactionsSorter transactions = new GasPricePendingTransactionsSorter( - maxTransactionRetentionHours, - MAX_TRANSACTIONS, + ImmutableTransactionPoolConfiguration.builder() + .pendingTxRetentionPeriod(1) + .txPoolMaxSize(MAX_TRANSACTIONS) + .build(), clock, metricsSystem, - () -> null, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP); + () -> null); transactions.addRemoteTransaction(transaction1); assertThat(transactions.size()).isEqualTo(1); clock.step(2L, ChronoUnit.HOURS); @@ -652,15 +674,15 @@ public void shouldEvictSingleOldTransaction() { @Test public void shouldEvictExclusivelyOldTransactions() { - final int maxTransactionRetentionHours = 2; final GasPricePendingTransactionsSorter transactions = new GasPricePendingTransactionsSorter( - maxTransactionRetentionHours, - MAX_TRANSACTIONS, + ImmutableTransactionPoolConfiguration.builder() + .pendingTxRetentionPeriod(2) + .txPoolMaxSize(MAX_TRANSACTIONS) + .build(), clock, metricsSystem, - () -> null, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP); + () -> null); transactions.addRemoteTransaction(transaction1); assertThat(transactions.size()).isEqualTo(1); clock.step(3L, ChronoUnit.HOURS); @@ -710,9 +732,11 @@ public void assertThatCorrectNonceIsReturned() { .isPresent() .hasValue(6); addLocalTransactions(6, 10); + + // assert that transactions are pruned by account from latest future nonces first assertThat(transactions.getNextNonceForSender(transaction1.getSender())) .isPresent() - .hasValue(7); + .hasValue(6); } @Test diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PendingMultiTypesTransactionsTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PendingMultiTypesTransactionsTest.java index 29d49e87d22..a1aba4b07ed 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PendingMultiTypesTransactionsTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PendingMultiTypesTransactionsTest.java @@ -31,6 +31,7 @@ import org.hyperledger.besu.plugin.data.TransactionType; import org.hyperledger.besu.testutil.TestClock; +import java.time.ZoneId; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -42,11 +43,15 @@ public class PendingMultiTypesTransactionsTest { private static final int MAX_TRANSACTIONS = 5; + private static final int MAX_TRANSACTIONS_BY_SENDER = 4; private static final Supplier SIGNATURE_ALGORITHM = Suppliers.memoize(SignatureAlgorithmFactory::getInstance)::get; private static final KeyPair KEYS1 = SIGNATURE_ALGORITHM.get().generateKeyPair(); private static final KeyPair KEYS2 = SIGNATURE_ALGORITHM.get().generateKeyPair(); private static final KeyPair KEYS3 = SIGNATURE_ALGORITHM.get().generateKeyPair(); + private static final KeyPair KEYS4 = SIGNATURE_ALGORITHM.get().generateKeyPair(); + private static final KeyPair KEYS5 = SIGNATURE_ALGORITHM.get().generateKeyPair(); + private static final KeyPair KEYS6 = SIGNATURE_ALGORITHM.get().generateKeyPair(); private static final String ADDED_COUNTER = "transactions_added_total"; private static final String REMOTE = "remote"; private static final String LOCAL = "local"; @@ -56,12 +61,13 @@ public class PendingMultiTypesTransactionsTest { private final StubMetricsSystem metricsSystem = new StubMetricsSystem(); private final BaseFeePendingTransactionsSorter transactions = new BaseFeePendingTransactionsSorter( - TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, - MAX_TRANSACTIONS, - TestClock.fixed(), + ImmutableTransactionPoolConfiguration.builder() + .txPoolMaxSize(MAX_TRANSACTIONS) + .txPoolMaxFutureTransactionByAccount(MAX_TRANSACTIONS_BY_SENDER) + .build(), + TestClock.system(ZoneId.systemDefault()), metricsSystem, - () -> mockBlockHeader(Wei.of(7L)), - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP); + () -> mockBlockHeader(Wei.of(7L))); @Test public void shouldReturnExclusivelyLocal1559TransactionsWhenAppropriate() { @@ -83,11 +89,11 @@ public void shouldReturnExclusivelyLocal1559TransactionsWhenAppropriate() { @Test public void shouldReplaceTransactionWithLowestMaxFeePerGas() { final Transaction localTransaction0 = create1559Transaction(0, 200, 20, KEYS1); - final Transaction localTransaction1 = create1559Transaction(1, 190, 20, KEYS1); - final Transaction localTransaction2 = create1559Transaction(2, 220, 20, KEYS1); - final Transaction localTransaction3 = create1559Transaction(3, 240, 20, KEYS1); - final Transaction localTransaction4 = create1559Transaction(4, 260, 20, KEYS1); - final Transaction localTransaction5 = create1559Transaction(5, 900, 20, KEYS1); + final Transaction localTransaction1 = create1559Transaction(0, 190, 20, KEYS2); + final Transaction localTransaction2 = create1559Transaction(0, 220, 20, KEYS3); + final Transaction localTransaction3 = create1559Transaction(0, 240, 20, KEYS4); + final Transaction localTransaction4 = create1559Transaction(0, 260, 20, KEYS5); + final Transaction localTransaction5 = create1559Transaction(0, 900, 20, KEYS6); transactions.addLocalTransaction(localTransaction0); transactions.addLocalTransaction(localTransaction1); transactions.addLocalTransaction(localTransaction2); @@ -109,11 +115,11 @@ public void shouldReplaceTransactionWithLowestMaxFeePerGas() { @Test public void shouldEvictTransactionWithLowestMaxFeePerGasAndLowestTip() { final Transaction localTransaction0 = create1559Transaction(0, 200, 20, KEYS1); - final Transaction localTransaction1 = create1559Transaction(1, 200, 19, KEYS1); - final Transaction localTransaction2 = create1559Transaction(2, 200, 18, KEYS1); - final Transaction localTransaction3 = create1559Transaction(3, 240, 20, KEYS1); - final Transaction localTransaction4 = create1559Transaction(4, 260, 20, KEYS1); - final Transaction localTransaction5 = create1559Transaction(5, 900, 20, KEYS1); + final Transaction localTransaction1 = create1559Transaction(0, 200, 19, KEYS2); + final Transaction localTransaction2 = create1559Transaction(0, 200, 18, KEYS3); + final Transaction localTransaction3 = create1559Transaction(0, 240, 20, KEYS4); + final Transaction localTransaction4 = create1559Transaction(0, 260, 20, KEYS5); + final Transaction localTransaction5 = create1559Transaction(0, 900, 20, KEYS6); transactions.addLocalTransaction(localTransaction0); transactions.addLocalTransaction(localTransaction1); transactions.addLocalTransaction(localTransaction2); @@ -133,11 +139,11 @@ public void shouldEvictTransactionWithLowestMaxFeePerGasAndLowestTip() { @Test public void shouldEvictLegacyTransactionWithLowestEffectiveMaxPriorityFeePerGas() { final Transaction localTransaction0 = create1559Transaction(0, 200, 20, KEYS1); - final Transaction localTransaction1 = createLegacyTransaction(1, 25, KEYS1); - final Transaction localTransaction2 = create1559Transaction(2, 200, 18, KEYS1); - final Transaction localTransaction3 = create1559Transaction(3, 240, 20, KEYS1); - final Transaction localTransaction4 = create1559Transaction(4, 260, 20, KEYS1); - final Transaction localTransaction5 = create1559Transaction(5, 900, 20, KEYS1); + final Transaction localTransaction1 = createLegacyTransaction(0, 25, KEYS2); + final Transaction localTransaction2 = create1559Transaction(0, 200, 18, KEYS3); + final Transaction localTransaction3 = create1559Transaction(0, 240, 20, KEYS4); + final Transaction localTransaction4 = create1559Transaction(0, 260, 20, KEYS5); + final Transaction localTransaction5 = create1559Transaction(0, 900, 20, KEYS6); transactions.addLocalTransaction(localTransaction0); transactions.addLocalTransaction(localTransaction1); transactions.addLocalTransaction(localTransaction2); @@ -156,11 +162,11 @@ public void shouldEvictLegacyTransactionWithLowestEffectiveMaxPriorityFeePerGas( @Test public void shouldEvictEIP1559TransactionWithLowestEffectiveMaxPriorityFeePerGas() { final Transaction localTransaction0 = create1559Transaction(0, 200, 20, KEYS1); - final Transaction localTransaction1 = createLegacyTransaction(1, 26, KEYS1); - final Transaction localTransaction2 = create1559Transaction(2, 200, 18, KEYS1); - final Transaction localTransaction3 = create1559Transaction(3, 240, 20, KEYS1); - final Transaction localTransaction4 = create1559Transaction(4, 260, 20, KEYS1); - final Transaction localTransaction5 = create1559Transaction(5, 900, 20, KEYS1); + final Transaction localTransaction1 = createLegacyTransaction(0, 26, KEYS2); + final Transaction localTransaction2 = create1559Transaction(0, 200, 18, KEYS3); + final Transaction localTransaction3 = create1559Transaction(0, 240, 20, KEYS4); + final Transaction localTransaction4 = create1559Transaction(0, 260, 20, KEYS5); + final Transaction localTransaction5 = create1559Transaction(0, 900, 20, KEYS6); transactions.addLocalTransaction(localTransaction0); transactions.addLocalTransaction(localTransaction1); transactions.addLocalTransaction(localTransaction2); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TestNode.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TestNode.java index b7726d4c18f..515315c4e37 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TestNode.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TestNode.java @@ -64,6 +64,7 @@ import java.io.Closeable; import java.io.IOException; import java.math.BigInteger; +import java.time.ZoneId; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -141,7 +142,7 @@ public TestNode( protocolSchedule, protocolContext, ethContext, - TestClock.fixed(), + TestClock.system(ZoneId.systemDefault()), metricsSystem, syncState::isInitialSyncPhaseDone, new MiningParameters.Builder().minTransactionGasPrice(Wei.ZERO).build(), diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactoryTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactoryTest.java index b47080abcc9..525a5b219e2 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactoryTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactoryTest.java @@ -91,14 +91,11 @@ public void testDisconnect() { new NoOpMetricsSystem(), () -> true, new MiningParameters.Builder().minTransactionGasPrice(Wei.ONE).build(), - ImmutableTransactionPoolConfiguration.of( - 1, - 1, - 1, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP, - TransactionPoolConfiguration.ETH65_TRX_ANNOUNCED_BUFFERING_PERIOD, - TransactionPoolConfiguration.DEFAULT_RPC_TX_FEE_CAP, - TransactionPoolConfiguration.DEFAULT_STRICT_TX_REPLAY_PROTECTION_ENABLED), + ImmutableTransactionPoolConfiguration.builder() + .txPoolMaxSize(1) + .txMessageKeepAliveSeconds(1) + .pendingTxRetentionPeriod(1) + .build(), pendingTransactions, peerTransactionTracker, transactionsMessageSender, diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolTest.java index 72e9bc47689..a847aaa9d41 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolTest.java @@ -77,6 +77,7 @@ import org.hyperledger.besu.testutil.TestClock; import java.math.BigInteger; +import java.time.ZoneId; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -133,12 +134,10 @@ public void setUp() { blockchain = executionContext.getBlockchain(); transactions = new GasPricePendingTransactionsSorter( - TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, - MAX_TRANSACTIONS, - TestClock.fixed(), + ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(MAX_TRANSACTIONS).build(), + TestClock.system(ZoneId.systemDefault()), metricsSystem, - blockchain::getChainHeadHeader, - TransactionPoolConfiguration.DEFAULT_PRICE_BUMP); + blockchain::getChainHeadHeader); when(protocolSchedule.getByBlockNumber(anyLong())).thenReturn(protocolSpec); when(protocolSpec.getTransactionValidator()).thenReturn(transactionValidator); when(protocolSpec.getFeeMarket()).thenReturn(FeeMarket.legacy()); diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/RlpxAgent.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/RlpxAgent.java index b9db0cbf0df..81fe8ad7e8e 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/RlpxAgent.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/RlpxAgent.java @@ -554,7 +554,7 @@ private int compareDuplicateConnections(final RlpxConnection a, final RlpxConnec } } // Otherwise, keep older connection - LOG.info("comparing timestamps " + a.getInitiatedAt() + " with " + b.getInitiatedAt()); + LOG.debug("comparing timestamps " + a.getInitiatedAt() + " with " + b.getInitiatedAt()); return Math.toIntExact(a.getInitiatedAt() - b.getInitiatedAt()); } diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/DeFramer.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/DeFramer.java index 44d325d7329..5c78124b98f 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/DeFramer.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/DeFramer.java @@ -184,7 +184,7 @@ protected void decode(final ChannelHandlerContext ctx, final ByteBuf in, final L } else { // Unexpected message - disconnect LOG.debug( - "Message received before HELLO's exchanged, disconnecting. Peer: {}, Code: {}, Data: {}", + "Message received before HELLO's exchanged (BREACH_OF_PROTOCOL), disconnecting. Peer: {}, Code: {}, Data: {}", expectedPeer.map(Peer::getEnodeURLString).orElse("unknown"), message.getCode(), message.getData().toString()); @@ -227,7 +227,7 @@ public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable thr if (cause instanceof FramingException || cause instanceof RLPException || cause instanceof IllegalArgumentException) { - LOG.debug("Invalid incoming message", throwable); + LOG.debug("Invalid incoming message (BREACH_OF_PROTOCOL)", throwable); if (connectFuture.isDone() && !connectFuture.isCompletedExceptionally()) { connectFuture.get().disconnect(DisconnectMessage.DisconnectReason.BREACH_OF_PROTOCOL); return; diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/WireMessagesSedesTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/WireMessagesSedesTest.java index e3e3ffcd82f..6adbd1b24bc 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/WireMessagesSedesTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/WireMessagesSedesTest.java @@ -34,7 +34,7 @@ public void deserializeHello() { + "b35761b0fe3f0de19cb96092be29a0d0c033a1629d3cf270345586679aba8bbda61069532e3ac7551fc3a9" + "7766c30037184a5bed48a821861"); - assertSedesWorks(rlp); + assertSedesWorks(rlp, true); rlp = decodeHexDump( @@ -43,11 +43,21 @@ public void deserializeHello() { + "dd83a6a6580b2559c3c2d87527b83ea8f232ddeed2fff3263949105761ab5d0fe3733046e0e75aaa83cada" + "3b1e5d41"); - assertSedesWorks(rlp); + assertSedesWorks(rlp, true); + + rlp = + decodeHexDump( + "f8a305b74e65746865726d696e642f76312e31332e342d302d3365353937326332342d32303232303831312f5836342d4c696e75782f362e302e36e4c5836574683ec5836574683fc58365746840c58365746841c58365746842c5837769748082765db84005c95b2618ba1ca53f0f019d1750d12769267705e46b4dbfb77f73998b21d30973161542a2090bcaa5876e6aed99436009f3f646029bb723b8dff75feec27374"); + // This test conatains a hello message from Nethermind. The Capability version of the wit + // capability is encoded as 0x80, which is an empty string + assertSedesWorks(rlp, false); } - private static void assertSedesWorks(final byte[] data) { + private static void assertSedesWorks(final byte[] data, final boolean encEqualsInput) { + // TODO: the boolean was added because we do encode the version number 0 as 0x00, not as 0x80 + // Ticket #4284 to address the broader issue of encoding/decoding zero final Bytes input = Bytes.wrap(data); + final PeerInfo peerInfo = PeerInfo.readFrom(RLP.input(input)); assertThat(peerInfo.getClientId()).isNotBlank(); @@ -59,6 +69,8 @@ private static void assertSedesWorks(final byte[] data) { // Re-serialize and check that data matches final BytesValueRLPOutput out = new BytesValueRLPOutput(); peerInfo.writeTo(out); - assertThat(out.encoded()).isEqualTo(input); + if (encEqualsInput) { + assertThat(out.encoded()).isEqualTo(input); + } } } diff --git a/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/AbstractRLPInput.java b/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/AbstractRLPInput.java index 584ecf373d9..65c07297fae 100644 --- a/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/AbstractRLPInput.java +++ b/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/AbstractRLPInput.java @@ -129,13 +129,13 @@ private void prepareCurrentItem() { // Sets the kind of the item, the offset at which his payload starts and the size of this // payload. try { - RLPDecodingHelpers.RLPElementMetadata elementMetadata = + final RLPDecodingHelpers.RLPElementMetadata elementMetadata = RLPDecodingHelpers.rlpElementMetadata(this::inputByte, size, currentItem); currentKind = elementMetadata.kind; currentPayloadOffset = elementMetadata.payloadStart; currentPayloadSize = elementMetadata.payloadSize; - } catch (RLPException exception) { - String message = + } catch (final RLPException exception) { + final String message = String.format( exception.getMessage() + getErrorMessageSuffix(), getErrorMessageSuffixParams()); throw new RLPException(message, exception); @@ -526,6 +526,11 @@ public boolean isEndOfCurrentList() { return depth > 0 && currentItem >= endOfListOffset[depth - 1]; } + @Override + public boolean isZeroLengthString() { + return currentKind == RLPDecodingHelpers.Kind.SHORT_ELEMENT && currentPayloadSize == 0; + } + @Override public void reset() { setTo(0); diff --git a/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPInput.java b/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPInput.java index 9fa5d53d3fb..49b01c65404 100644 --- a/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPInput.java +++ b/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPInput.java @@ -221,6 +221,11 @@ public interface RLPInput { * long. */ default int readUnsignedByte() { + if (isZeroLengthString()) { + // Decode an empty string (0x80) as an unsigned byte with value 0 + readBytes(); + return 0; + } return readByte() & 0xFF; } @@ -276,7 +281,7 @@ default long readUnsignedInt() { Bytes32 readBytes32(); /** - * Reads the next iterm of this input (assuming it is not a list) and transform it with the + * Reads the next item of this input (assuming it is not a list) and transforms it with the * provided mapping function. * *

Note that the only benefit of this method over calling the mapper function on the result of @@ -311,6 +316,8 @@ default long readUnsignedInt() { */ Bytes raw(); + boolean isZeroLengthString(); + /** Resets this RLP input to the start. */ void reset(); diff --git a/gradle.properties b/gradle.properties index 1fa78b2c536..5da786e14c7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=22.7.1 +version=22.7.2-SNAPSHOT org.gradle.welcome=never -org.gradle.jvmargs=-Xmx1g \ No newline at end of file +org.gradle.jvmargs=-Xmx1g diff --git a/util/src/main/java/org/hyperledger/besu/util/Subscribers.java b/util/src/main/java/org/hyperledger/besu/util/Subscribers.java index acadcb73613..507167fa012 100644 --- a/util/src/main/java/org/hyperledger/besu/util/Subscribers.java +++ b/util/src/main/java/org/hyperledger/besu/util/Subscribers.java @@ -112,7 +112,8 @@ public void forEach(final Consumer action) { action.accept(subscriber); } catch (final Exception e) { if (suppressCallbackExceptions) { - LOG.error("Error in callback: ", e); + LOG.error("Error in callback: {}", e.getMessage()); + LOG.debug("Error in callback: ", e); } else { throw e; }