diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/AcceptanceTestBase.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/AcceptanceTestBase.java index be0f7bb5a9..1547b03267 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/AcceptanceTestBase.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/AcceptanceTestBase.java @@ -24,6 +24,7 @@ import org.hyperledger.besu.tests.acceptance.dsl.condition.login.LoginConditions import org.hyperledger.besu.tests.acceptance.dsl.condition.net.NetConditions; import org.hyperledger.besu.tests.acceptance.dsl.condition.perm.PermissioningConditions; import org.hyperledger.besu.tests.acceptance.dsl.condition.priv.PrivConditions; +import org.hyperledger.besu.tests.acceptance.dsl.condition.txpool.TxPoolConditions; import org.hyperledger.besu.tests.acceptance.dsl.condition.web3.Web3Conditions; import org.hyperledger.besu.tests.acceptance.dsl.contract.ContractVerifier; import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.Cluster; @@ -39,6 +40,7 @@ import org.hyperledger.besu.tests.acceptance.dsl.transaction.miner.MinerTransact import org.hyperledger.besu.tests.acceptance.dsl.transaction.net.NetTransactions; import org.hyperledger.besu.tests.acceptance.dsl.transaction.perm.PermissioningTransactions; import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivacyTransactions; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.txpool.TxPoolTransactions; import org.hyperledger.besu.tests.acceptance.dsl.transaction.web3.Web3Transactions; import java.io.File; @@ -83,6 +85,8 @@ public class AcceptanceTestBase { protected final Web3Conditions web3; protected final PrivConditions priv; protected final PrivacyTransactions privacyTransactions; + protected final TxPoolConditions txPoolConditions; + protected final TxPoolTransactions txPoolTransactions; protected AcceptanceTestBase() { ethTransactions = new EthTransactions(); @@ -108,6 +112,8 @@ public class AcceptanceTestBase { admin = new AdminConditions(adminTransactions); web3 = new Web3Conditions(new Web3Transactions()); besu = new BesuNodeFactory(); + txPoolTransactions = new TxPoolTransactions(); + txPoolConditions = new TxPoolConditions(txPoolTransactions); contractVerifier = new ContractVerifier(accounts.getPrimaryBenefactor()); permissionedNodeBuilder = new PermissionedNodeBuilder(); } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/txpool/TxPoolConditions.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/txpool/TxPoolConditions.java new file mode 100644 index 0000000000..da7dea8e2e --- /dev/null +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/txpool/TxPoolConditions.java @@ -0,0 +1,51 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.dsl.condition.txpool; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.tests.acceptance.dsl.WaitUtils; +import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.txpool.TxPoolTransactions; + +import java.util.List; +import java.util.Map; + +public class TxPoolConditions { + + private final TxPoolTransactions txPoolTransactions; + + public TxPoolConditions(final TxPoolTransactions txPoolTransactions) { + this.txPoolTransactions = txPoolTransactions; + } + + public Condition inTransactionPool(final Hash txHash) { + return node -> + WaitUtils.waitFor( + () -> { + List> poolContents = + node.execute(txPoolTransactions.getTxPoolContents()); + boolean found = false; + for (Map txInfo : poolContents) { + if (Hash.fromHexString(txInfo.get("hash")).equals(txHash)) { + found = true; + break; + } + } + assertThat(found).isTrue(); + }); + } +} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/BesuNode.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/BesuNode.java index b119d0c833..b5a8cf01d3 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/BesuNode.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/BesuNode.java @@ -42,6 +42,7 @@ import org.hyperledger.besu.tests.acceptance.dsl.transaction.miner.MinerRequestF import org.hyperledger.besu.tests.acceptance.dsl.transaction.net.CustomRequestFactory; import org.hyperledger.besu.tests.acceptance.dsl.transaction.perm.PermissioningJsonRpcRequestFactory; import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivacyRequestFactory; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.txpool.TxPoolRequestFactory; import java.io.File; import java.io.FileInputStream; @@ -330,6 +331,7 @@ public class BesuNode implements NodeConfiguration, RunnableNode, AutoCloseable new PrivacyRequestFactory(web3jService), new CustomRequestFactory(web3jService), new MinerRequestFactory(web3jService), + new TxPoolRequestFactory(web3jService), websocketService, loginRequestFactory()); } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfigurationBuilder.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfigurationBuilder.java index b52f035f04..69ef938766 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfigurationBuilder.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfigurationBuilder.java @@ -120,6 +120,11 @@ public class BesuNodeConfigurationBuilder { return this; } + public BesuNodeConfigurationBuilder jsonRpcTxPool() { + this.jsonRpcConfiguration.addRpcApi(RpcApis.TX_POOL); + return this; + } + public BesuNodeConfigurationBuilder jsonRpcAuthenticationConfiguration(final String authFile) throws URISyntaxException { final String authTomlPath = diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java index e5c64f5be3..a58744eaf2 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java @@ -92,7 +92,12 @@ public class BesuNodeFactory { public BesuNode createArchiveNode(final String name) throws IOException { return create( - new BesuNodeConfigurationBuilder().name(name).jsonRpcEnabled().webSocketEnabled().build()); + new BesuNodeConfigurationBuilder() + .name(name) + .jsonRpcEnabled() + .jsonRpcTxPool() + .webSocketEnabled() + .build()); } public BesuNode createNode( diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/NodeRequests.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/NodeRequests.java index 2534a44b17..46cdf61bd0 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/NodeRequests.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/NodeRequests.java @@ -22,6 +22,7 @@ import org.hyperledger.besu.tests.acceptance.dsl.transaction.miner.MinerRequestF import org.hyperledger.besu.tests.acceptance.dsl.transaction.net.CustomRequestFactory; import org.hyperledger.besu.tests.acceptance.dsl.transaction.perm.PermissioningJsonRpcRequestFactory; import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivacyRequestFactory; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.txpool.TxPoolRequestFactory; import java.util.Optional; @@ -40,6 +41,7 @@ public class NodeRequests { private final Optional websocketService; private final LoginRequestFactory login; private final MinerRequestFactory miner; + private final TxPoolRequestFactory txPool; public NodeRequests( final Web3j netEth, @@ -50,6 +52,7 @@ public class NodeRequests { final PrivacyRequestFactory privacy, final CustomRequestFactory custom, final MinerRequestFactory miner, + final TxPoolRequestFactory txPool, final Optional websocketService, final LoginRequestFactory login) { this.netEth = netEth; @@ -60,6 +63,7 @@ public class NodeRequests { this.privacy = privacy; this.custom = custom; this.miner = miner; + this.txPool = txPool; this.websocketService = websocketService; this.login = login; } @@ -104,6 +108,10 @@ public class NodeRequests { return miner; } + public TxPoolRequestFactory txPool() { + return txPool; + } + public void shutdown() { netEth.shutdown(); websocketService.ifPresent(WebSocketService::close); diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/txpool/TxPoolBesuTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/txpool/TxPoolBesuTransaction.java new file mode 100644 index 0000000000..5799a10f09 --- /dev/null +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/txpool/TxPoolBesuTransaction.java @@ -0,0 +1,34 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.dsl.transaction.txpool; + +import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public class TxPoolBesuTransaction implements Transaction>> { + + @Override + public List> execute(final NodeRequests node) { + try { + return node.txPool().besuTransactions().send().getResult(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/txpool/TxPoolRequestFactory.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/txpool/TxPoolRequestFactory.java new file mode 100644 index 0000000000..d0d1574893 --- /dev/null +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/txpool/TxPoolRequestFactory.java @@ -0,0 +1,38 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.dsl.transaction.txpool; + +import java.util.List; +import java.util.Map; + +import org.web3j.protocol.Web3jService; +import org.web3j.protocol.core.Request; +import org.web3j.protocol.core.Response; + +public class TxPoolRequestFactory { + + private final Web3jService web3jService; + + public TxPoolRequestFactory(final Web3jService web3jService) { + this.web3jService = web3jService; + } + + Request besuTransactions() { + return new Request<>( + "txpool_besuTransactions", null, web3jService, TransactionInfoResponse.class); + } + + static class TransactionInfoResponse extends Response>> {} +} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/txpool/TxPoolTransactions.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/txpool/TxPoolTransactions.java new file mode 100644 index 0000000000..8d6c9d2050 --- /dev/null +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/txpool/TxPoolTransactions.java @@ -0,0 +1,22 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.dsl.transaction.txpool; + +public class TxPoolTransactions { + + public TxPoolBesuTransaction getTxPoolContents() { + return new TxPoolBesuTransaction(); + } +} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/GossipTransactionAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/GossipTransactionAcceptanceTest.java new file mode 100644 index 0000000000..32eac30de8 --- /dev/null +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/GossipTransactionAcceptanceTest.java @@ -0,0 +1,48 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance; + +import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; +import org.hyperledger.besu.tests.acceptance.dsl.account.Account; +import org.hyperledger.besu.tests.acceptance.dsl.blockchain.Amount; +import org.hyperledger.besu.tests.acceptance.dsl.node.Node; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.account.TransferTransaction; + +import org.junit.Before; +import org.junit.Test; + +public class GossipTransactionAcceptanceTest extends AcceptanceTestBase { + + private Node archiveNode1; + + @Before + public void setUp() throws Exception { + archiveNode1 = besu.createArchiveNode("archiveNode1"); + cluster.start(archiveNode1, besu.createArchiveNode("archiveNode2")); + } + + @Test + public void shouldGossipATransaction() { + final Account account = accounts.createAccount("account-one"); + final Amount balance = Amount.ether(20); + + TransferTransaction tx = accountTransactions.createTransfer(account, balance); + + Hash txHash = archiveNode1.execute(tx); + + cluster.verify(txPoolConditions.inTransactionPool(txHash)); + } +} 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 e09a90871e..4447d7dd34 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -787,6 +787,15 @@ public class BesuCommand implements DefaultCommandValues, Runnable { arity = "1") private final Integer txPoolMaxSize = TransactionPoolConfiguration.MAX_PENDING_TRANSACTIONS; + @Option( + names = {"--tx-pool-hashes-max-size"}, + paramLabel = MANDATORY_INTEGER_FORMAT_HELP, + description = + "Maximum number of pending transaction hashes that will be kept in the transaction pool (default: ${DEFAULT-VALUE})", + arity = "1") + private final Integer pooledTransactionHashesSize = + TransactionPoolConfiguration.MAX_PENDING_TRANSACTIONS_HASHES; + @Option( names = {"--tx-pool-retention-hours"}, paramLabel = MANDATORY_INTEGER_FORMAT_HELP, @@ -1690,6 +1699,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { return transactionPoolOptions .toDomainObject() .txPoolMaxSize(txPoolMaxSize) + .pooledTransactionHashesSize(pooledTransactionHashesSize) .pendingTxRetentionPeriod(pendingTxRetentionPeriod) .build(); } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/EthProtocolOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/EthProtocolOptions.java index 7dce250713..cd45a76859 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/EthProtocolOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/EthProtocolOptions.java @@ -27,6 +27,7 @@ public class EthProtocolOptions implements CLIOptions private static final String MAX_GET_BODIES_FLAG = "--Xewp-max-get-bodies"; private static final String MAX_GET_RECEIPTS_FLAG = "--Xewp-max-get-receipts"; private static final String MAX_GET_NODE_DATA_FLAG = "--Xewp-max-get-node-data"; + private static final String MAX_GET_POOLED_TRANSACTIONS = "--Xewp-max-get-pooled-transactions"; @CommandLine.Option( hidden = true, @@ -64,6 +65,15 @@ public class EthProtocolOptions implements CLIOptions private PositiveNumber maxGetNodeData = PositiveNumber.fromInt(EthProtocolConfiguration.DEFAULT_MAX_GET_NODE_DATA); + @CommandLine.Option( + hidden = true, + names = {MAX_GET_POOLED_TRANSACTIONS}, + paramLabel = "", + description = + "Maximum request limit for Ethereum Wire Protocol GET_POOLED_TRANSACTIONS. (default: ${DEFAULT-VALUE})") + private PositiveNumber maxGetPooledTransactions = + PositiveNumber.fromInt(EthProtocolConfiguration.DEFAULT_MAX_GET_POOLED_TRANSACTIONS); + private EthProtocolOptions() {} public static EthProtocolOptions create() { @@ -76,6 +86,7 @@ public class EthProtocolOptions implements CLIOptions options.maxGetBlockBodies = PositiveNumber.fromInt(config.getMaxGetBlockBodies()); options.maxGetReceipts = PositiveNumber.fromInt(config.getMaxGetReceipts()); options.maxGetNodeData = PositiveNumber.fromInt(config.getMaxGetNodeData()); + options.maxGetPooledTransactions = PositiveNumber.fromInt(config.getMaxGetPooledTransactions()); return options; } @@ -86,6 +97,7 @@ public class EthProtocolOptions implements CLIOptions .maxGetBlockBodies(maxGetBlockBodies) .maxGetReceipts(maxGetReceipts) .maxGetNodeData(maxGetNodeData) + .maxGetPooledTransactions(maxGetPooledTransactions) .build(); } @@ -99,6 +111,8 @@ public class EthProtocolOptions implements CLIOptions MAX_GET_RECEIPTS_FLAG, OptionParser.format(maxGetReceipts.getValue()), MAX_GET_NODE_DATA_FLAG, - OptionParser.format(maxGetNodeData.getValue())); + OptionParser.format(maxGetNodeData.getValue()), + MAX_GET_POOLED_TRANSACTIONS, + OptionParser.format(maxGetPooledTransactions.getValue())); } } 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 80945fe4e8..b3383fc978 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -31,7 +31,11 @@ import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.Synchronizer; import org.hyperledger.besu.ethereum.eth.EthProtocol; import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; +import org.hyperledger.besu.ethereum.eth.manager.EthContext; +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.EthScheduler; import org.hyperledger.besu.ethereum.eth.peervalidation.ClassicForkPeerValidator; import org.hyperledger.besu.ethereum.eth.peervalidation.DaoForkPeerValidator; import org.hyperledger.besu.ethereum.eth.peervalidation.PeerValidator; @@ -242,13 +246,40 @@ public abstract class BesuControllerBuilder { prunerConfiguration)); } } - + final EthPeers ethPeers = new EthPeers(getSupportedProtocol(), clock, metricsSystem); + final EthMessages ethMessages = new EthMessages(); + final EthScheduler scheduler = + new EthScheduler( + syncConfig.getDownloaderParallelism(), + syncConfig.getTransactionsParallelism(), + syncConfig.getComputationParallelism(), + metricsSystem); + final EthContext ethContext = new EthContext(ethPeers, ethMessages, scheduler); + final SyncState syncState = new SyncState(blockchain, ethPeers); final boolean fastSyncEnabled = syncConfig.getSyncMode().equals(SyncMode.FAST); + final TransactionPool transactionPool = + TransactionPoolFactory.createTransactionPool( + protocolSchedule, + protocolContext, + ethContext, + clock, + metricsSystem, + syncState, + miningParameters.getMinTransactionGasPrice(), + transactionPoolConfiguration); + final EthProtocolManager ethProtocolManager = createEthProtocolManager( - protocolContext, fastSyncEnabled, createPeerValidators(protocolSchedule)); - final SyncState syncState = - new SyncState(blockchain, ethProtocolManager.ethContext().getEthPeers()); + protocolContext, + fastSyncEnabled, + transactionPool, + ethereumWireProtocolConfiguration, + ethPeers, + ethContext, + ethMessages, + scheduler, + createPeerValidators(protocolSchedule)); + final Synchronizer synchronizer = new DefaultSynchronizer<>( syncConfig, @@ -263,17 +294,6 @@ public abstract class BesuControllerBuilder { clock, metricsSystem); - final TransactionPool transactionPool = - TransactionPoolFactory.createTransactionPool( - protocolSchedule, - protocolContext, - ethProtocolManager.ethContext(), - clock, - metricsSystem, - syncState, - miningParameters.getMinTransactionGasPrice(), - transactionPoolConfiguration); - final MiningCoordinator miningCoordinator = createMiningCoordinator( protocolSchedule, @@ -343,22 +363,32 @@ public abstract class BesuControllerBuilder { protected abstract C createConsensusContext( Blockchain blockchain, WorldStateArchive worldStateArchive); + protected String getSupportedProtocol() { + return EthProtocol.NAME; + } + protected EthProtocolManager createEthProtocolManager( final ProtocolContext protocolContext, final boolean fastSyncEnabled, + final TransactionPool transactionPool, + final EthProtocolConfiguration ethereumWireProtocolConfiguration, + final EthPeers ethPeers, + final EthContext ethContext, + final EthMessages ethMessages, + final EthScheduler scheduler, final List peerValidators) { return new EthProtocolManager( protocolContext.getBlockchain(), - protocolContext.getWorldStateArchive(), networkId, + protocolContext.getWorldStateArchive(), + transactionPool, + ethereumWireProtocolConfiguration, + ethPeers, + ethMessages, + ethContext, peerValidators, fastSyncEnabled, - syncConfig.getDownloaderParallelism(), - syncConfig.getTransactionsParallelism(), - syncConfig.getComputationParallelism(), - clock, - metricsSystem, - ethereumWireProtocolConfiguration, + scheduler, genesisConfig.getForks()); } diff --git a/besu/src/main/java/org/hyperledger/besu/controller/IbftLegacyBesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/IbftLegacyBesuControllerBuilder.java index ab29cbc26b..f13828835b 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/IbftLegacyBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/IbftLegacyBesuControllerBuilder.java @@ -31,7 +31,12 @@ import org.hyperledger.besu.ethereum.blockcreation.NoopMiningCoordinator; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; +import org.hyperledger.besu.ethereum.eth.manager.EthContext; +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.EthScheduler; import org.hyperledger.besu.ethereum.eth.peervalidation.PeerValidator; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -106,23 +111,34 @@ public class IbftLegacyBesuControllerBuilder extends BesuControllerBuilder protocolContext, final boolean fastSyncEnabled, + final TransactionPool transactionPool, + final EthProtocolConfiguration ethereumWireProtocolConfiguration, + final EthPeers ethPeers, + final EthContext ethContext, + final EthMessages ethMessages, + final EthScheduler scheduler, final List peerValidators) { LOG.info("Operating on IBFT-1.0 network."); return new Istanbul64ProtocolManager( protocolContext.getBlockchain(), - protocolContext.getWorldStateArchive(), networkId, + protocolContext.getWorldStateArchive(), + transactionPool, + ethereumWireProtocolConfiguration, + ethPeers, + ethMessages, + ethContext, peerValidators, fastSyncEnabled, - syncConfig.getDownloaderParallelism(), - syncConfig.getTransactionsParallelism(), - syncConfig.getComputationParallelism(), - clock, - metricsSystem, - ethereumWireProtocolConfiguration); + scheduler); } } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/EthProtocolOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/EthProtocolOptionsTest.java index 5ae661b81c..6b7832abae 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/options/EthProtocolOptionsTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/EthProtocolOptionsTest.java @@ -130,6 +130,9 @@ public class EthProtocolOptionsTest PositiveNumber.fromInt(EthProtocolConfiguration.DEFAULT_MAX_GET_RECEIPTS + 2)) .maxGetNodeData( PositiveNumber.fromInt(EthProtocolConfiguration.DEFAULT_MAX_GET_NODE_DATA + 2)) + .maxGetPooledTransactions( + PositiveNumber.fromInt( + EthProtocolConfiguration.DEFAULT_MAX_GET_POOLED_TRANSACTIONS + 2)) .build(); } 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 523604d12d..d78e120049 100644 --- a/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java +++ b/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java @@ -103,10 +103,15 @@ public class BesuEventsImplTest { new KeyValueStoragePrefixedKeyBlockchainStorage( new InMemoryKeyValueStorage(), new MainnetBlockHeaderFunctions()), new NoOpMetricsSystem()); + when(mockEthContext.getEthMessages()).thenReturn(mockEthMessages); when(mockEthContext.getEthPeers()).thenReturn(mockEthPeers); when(mockEthContext.getScheduler()).thenReturn(mockEthScheduler); - when(mockEthPeers.streamAvailablePeers()).thenReturn(Stream.empty()).thenReturn(Stream.empty()); + when(mockEthPeers.streamAvailablePeers()) + .thenReturn(Stream.empty()) + .thenReturn(Stream.empty()) + .thenReturn(Stream.empty()) + .thenReturn(Stream.empty()); when(mockProtocolContext.getBlockchain()).thenReturn(blockchain); when(mockProtocolContext.getWorldStateArchive()).thenReturn(mockWorldStateArchive); when(mockProtocolSchedule.getByBlockNumber(anyLong())).thenReturn(mockProtocolSpec); @@ -118,6 +123,9 @@ public class BesuEventsImplTest { blockBroadcaster = new BlockBroadcaster(mockEthContext); syncState = new SyncState(blockchain, mockEthPeers); + TransactionPoolConfiguration txPoolConfig = + TransactionPoolConfiguration.builder().txPoolMaxSize(1).build(); + transactionPool = TransactionPoolFactory.createTransactionPool( mockProtocolSchedule, @@ -127,7 +135,7 @@ public class BesuEventsImplTest { new NoOpMetricsSystem(), syncState, Wei.ZERO, - TransactionPoolConfiguration.builder().txPoolMaxSize(1).build()); + txPoolConfig); serviceImpl = new BesuEventsImpl(blockchain, blockBroadcaster, transactionPool, syncState); } diff --git a/besu/src/test/resources/everything_config.toml b/besu/src/test/resources/everything_config.toml index f5fa8d6a0e..4809ceebad 100644 --- a/besu/src/test/resources/everything_config.toml +++ b/besu/src/test/resources/everything_config.toml @@ -129,6 +129,7 @@ privacy-onchain-groups-enabled=false tx-pool-retention-hours=999 tx-pool-max-size=1234 +tx-pool-hashes-max-size=10000 Xincoming-tx-messages-keep-alive-seconds=60 # Revert Reason 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 fba867099c..740e5e5f7c 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 @@ -122,6 +122,7 @@ public class CliqueBlockCreatorTest { new PendingTransactions( TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, 5, + 5, TestClock.fixed(), metricsSystem), protocolContext, @@ -153,6 +154,7 @@ public class CliqueBlockCreatorTest { new PendingTransactions( TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, 5, + 5, TestClock.fixed(), metricsSystem), protocolContext, @@ -183,6 +185,7 @@ public class CliqueBlockCreatorTest { new PendingTransactions( TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, 5, + 5, TestClock.fixed(), metricsSystem), protocolContext, @@ -216,6 +219,7 @@ public class CliqueBlockCreatorTest { new PendingTransactions( TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, 5, + 5, TestClock.fixed(), metricsSystem), protocolContext, 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 bf60fc5fb0..5e158f20ca 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 @@ -97,6 +97,7 @@ public class CliqueMinerExecutorTest { new PendingTransactions( TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, 1, + 5, TestClock.fixed(), metricsSystem), proposerKeyPair, @@ -134,6 +135,7 @@ public class CliqueMinerExecutorTest { new PendingTransactions( TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, 1, + 5, TestClock.fixed(), metricsSystem), proposerKeyPair, @@ -171,6 +173,7 @@ public class CliqueMinerExecutorTest { new PendingTransactions( TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, 1, + 5, TestClock.fixed(), metricsSystem), proposerKeyPair, 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 39c8921447..fc3023656b 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 @@ -300,7 +300,7 @@ public class TestContextBuilder { final PendingTransactions pendingTransactions = new PendingTransactions( - TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, 1, clock, metricsSystem); + TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, 1, 1, clock, metricsSystem); final IbftBlockCreatorFactory blockCreatorFactory = new IbftBlockCreatorFactory( diff --git a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/IbftBlockCreatorTest.java b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/IbftBlockCreatorTest.java index e6c1c4899a..9a5cc8ed4e 100644 --- a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/IbftBlockCreatorTest.java +++ b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/IbftBlockCreatorTest.java @@ -87,6 +87,7 @@ public class IbftBlockCreatorTest { new PendingTransactions( TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, 1, + 5, TestClock.fixed(), metricsSystem); diff --git a/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/protocol/Istanbul64Protocol.java b/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/protocol/Istanbul64Protocol.java index e4c74c2426..2924b8fa51 100644 --- a/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/protocol/Istanbul64Protocol.java +++ b/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/protocol/Istanbul64Protocol.java @@ -16,6 +16,7 @@ package org.hyperledger.besu.consensus.ibftlegacy.protocol; import org.hyperledger.besu.ethereum.eth.messages.EthPV62; import org.hyperledger.besu.ethereum.eth.messages.EthPV63; +import org.hyperledger.besu.ethereum.eth.messages.EthPV65; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Capability; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.SubProtocol; @@ -47,6 +48,9 @@ public class Istanbul64Protocol implements SubProtocol { EthPV62.GET_BLOCK_BODIES, EthPV62.BLOCK_BODIES, EthPV62.NEW_BLOCK, + EthPV65.NEW_POOLED_TRANSACTION_HASHES, + EthPV65.GET_POOLED_TRANSACTIONS, + EthPV65.POOLED_TRANSACTIONS, EthPV63.GET_NODE_DATA, EthPV63.NODE_DATA, EthPV63.GET_RECEIPTS, @@ -90,6 +94,12 @@ public class Istanbul64Protocol implements SubProtocol { return "BlockBodies"; case EthPV62.NEW_BLOCK: return "NewBlock"; + case EthPV65.NEW_POOLED_TRANSACTION_HASHES: + return "NewPooledTransactionHashes"; + case EthPV65.GET_POOLED_TRANSACTIONS: + return "GetPooledTransactions"; + case EthPV65.POOLED_TRANSACTIONS: + return "PooledTransactions"; case EthPV63.GET_NODE_DATA: return "GetNodeData"; case EthPV63.NODE_DATA: diff --git a/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/protocol/Istanbul64ProtocolManager.java b/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/protocol/Istanbul64ProtocolManager.java index d7eb7af0af..369563d3d9 100644 --- a/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/protocol/Istanbul64ProtocolManager.java +++ b/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/protocol/Istanbul64ProtocolManager.java @@ -18,14 +18,17 @@ import static java.util.Collections.singletonList; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; +import org.hyperledger.besu.ethereum.eth.manager.EthContext; +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.EthScheduler; import org.hyperledger.besu.ethereum.eth.peervalidation.PeerValidator; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Capability; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; -import org.hyperledger.besu.plugin.services.MetricsSystem; import java.math.BigInteger; -import java.time.Clock; import java.util.List; /** This allows for interoperability with Quorum, but shouldn't be used otherwise. */ @@ -33,28 +36,28 @@ public class Istanbul64ProtocolManager extends EthProtocolManager { public Istanbul64ProtocolManager( final Blockchain blockchain, - final WorldStateArchive worldStateArchive, final BigInteger networkId, + final WorldStateArchive worldStateArchive, + final TransactionPool transactionPool, + final EthProtocolConfiguration ethereumWireProtocolConfiguration, + final EthPeers ethPeers, + final EthMessages ethMessages, + final EthContext ethContext, final List peerValidators, final boolean fastSyncEnabled, - final int syncWorkers, - final int txWorkers, - final int computationWorkers, - final Clock clock, - final MetricsSystem metricsSystem, - final EthProtocolConfiguration ethereumWireProtocolConfiguration) { + final EthScheduler scheduler) { super( blockchain, - worldStateArchive, networkId, + worldStateArchive, + transactionPool, + ethereumWireProtocolConfiguration, + ethPeers, + ethMessages, + ethContext, peerValidators, fastSyncEnabled, - syncWorkers, - txWorkers, - computationWorkers, - clock, - metricsSystem, - ethereumWireProtocolConfiguration); + scheduler); } @Override diff --git a/consensus/ibftlegacy/src/test/java/org/hyperledger/besu/consensus/ibftlegacy/blockcreation/IbftBlockCreatorTest.java b/consensus/ibftlegacy/src/test/java/org/hyperledger/besu/consensus/ibftlegacy/blockcreation/IbftBlockCreatorTest.java index dd007f3123..87ad06deca 100644 --- a/consensus/ibftlegacy/src/test/java/org/hyperledger/besu/consensus/ibftlegacy/blockcreation/IbftBlockCreatorTest.java +++ b/consensus/ibftlegacy/src/test/java/org/hyperledger/besu/consensus/ibftlegacy/blockcreation/IbftBlockCreatorTest.java @@ -101,6 +101,7 @@ public class IbftBlockCreatorTest { new PendingTransactions( TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, 1, + 5, TestClock.fixed(), metricsSystem), protContext, diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthGetFilterChangesIntegrationTest.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthGetFilterChangesIntegrationTest.java index 54e5ed91ab..a5cc1da7c6 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthGetFilterChangesIntegrationTest.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthGetFilterChangesIntegrationTest.java @@ -48,6 +48,7 @@ import org.hyperledger.besu.ethereum.core.Wei; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthPeers; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; +import org.hyperledger.besu.ethereum.eth.transactions.PeerPendingTransactionTracker; import org.hyperledger.besu.ethereum.eth.transactions.PeerTransactionTracker; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -73,6 +74,7 @@ import org.mockito.junit.MockitoJUnitRunner; public class EthGetFilterChangesIntegrationTest { @Mock private TransactionBatchAddedListener batchAddedListener; + @Mock private TransactionBatchAddedListener pendingBatchAddedListener; private MutableBlockchain blockchain; private final String ETH_METHOD = "eth_getFilterChanges"; private final String JSON_RPC_VERSION = "2.0"; @@ -83,10 +85,12 @@ public class EthGetFilterChangesIntegrationTest { new PendingTransactions( TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, MAX_TRANSACTIONS, + MAX_HASHES, TestClock.fixed(), metricsSystem); private static final int MAX_TRANSACTIONS = 5; + private static final int MAX_HASHES = 5; private static final KeyPair keyPair = KeyPair.generate(); private final Transaction transaction = createTransaction(1); private FilterManager filterManager; @@ -100,6 +104,8 @@ public class EthGetFilterChangesIntegrationTest { final ProtocolContext protocolContext = executionContext.getProtocolContext(); PeerTransactionTracker peerTransactionTracker = mock(PeerTransactionTracker.class); + PeerPendingTransactionTracker peerPendingTransactionTracker = + mock(PeerPendingTransactionTracker.class); EthContext ethContext = mock(EthContext.class); EthPeers ethPeers = mock(EthPeers.class); when(ethContext.getEthPeers()).thenReturn(ethPeers); @@ -110,9 +116,11 @@ public class EthGetFilterChangesIntegrationTest { executionContext.getProtocolSchedule(), protocolContext, batchAddedListener, + pendingBatchAddedListener, syncState, ethContext, peerTransactionTracker, + peerPendingTransactionTracker, Wei.ZERO, metricsSystem); final BlockchainQueries blockchainQueries = 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 f7d9af975a..64ae06f5a2 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 @@ -72,6 +72,7 @@ public class BlockTransactionSelectorTest { new PendingTransactions( TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, 5, + 5, TestClock.fixed(), metricsSystem); private final Blockchain blockchain = new TestBlockchain(); diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/EthHashBlockCreatorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/EthHashBlockCreatorTest.java index 3a0ed0c08d..e706c7a9d6 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/EthHashBlockCreatorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/EthHashBlockCreatorTest.java @@ -82,6 +82,7 @@ public class EthHashBlockCreatorTest { new PendingTransactions( TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, 1, + 5, TestClock.fixed(), metricsSystem); @@ -131,6 +132,7 @@ public class EthHashBlockCreatorTest { new PendingTransactions( TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, 1, + 5, TestClock.fixed(), metricsSystem); @@ -175,6 +177,7 @@ public class EthHashBlockCreatorTest { new PendingTransactions( TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, 1, + 5, TestClock.fixed(), metricsSystem); @@ -235,6 +238,7 @@ public class EthHashBlockCreatorTest { new PendingTransactions( TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, 1, + 5, TestClock.fixed(), metricsSystem); diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/EthHashMinerExecutorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/EthHashMinerExecutorTest.java index 4809752c20..a6905fd32f 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/EthHashMinerExecutorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/EthHashMinerExecutorTest.java @@ -41,6 +41,7 @@ public class EthHashMinerExecutorTest { new PendingTransactions( TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, 1, + 5, TestClock.fixed(), metricsSystem); @@ -66,6 +67,7 @@ public class EthHashMinerExecutorTest { new PendingTransactions( TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, 1, + 5, TestClock.fixed(), metricsSystem); diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockchainSetupUtil.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockchainSetupUtil.java index 4d9503e39d..ac6877fa88 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockchainSetupUtil.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockchainSetupUtil.java @@ -17,6 +17,7 @@ package org.hyperledger.besu.ethereum.core; import static org.assertj.core.util.Preconditions.checkArgument; import static org.hyperledger.besu.ethereum.core.InMemoryStorageProvider.createInMemoryBlockchain; import static org.hyperledger.besu.ethereum.core.InMemoryStorageProvider.createInMemoryWorldStateArchive; +import static org.mockito.Mockito.mock; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.ethereum.ProtocolContext; @@ -24,6 +25,7 @@ import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.GenesisState; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; import org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; @@ -52,6 +54,7 @@ public class BlockchainSetupUtil { private final ProtocolContext protocolContext; private final ProtocolSchedule protocolSchedule; private final WorldStateArchive worldArchive; + private final TransactionPool transactionPool; private final List blocks; private final EthScheduler scheduler; private long maxBlockNumber; @@ -62,6 +65,7 @@ public class BlockchainSetupUtil { final ProtocolContext protocolContext, final ProtocolSchedule protocolSchedule, final WorldStateArchive worldArchive, + final TransactionPool transactionPool, final List blocks, final EthScheduler scheduler) { this.genesisState = genesisState; @@ -69,6 +73,7 @@ public class BlockchainSetupUtil { this.protocolContext = protocolContext; this.protocolSchedule = protocolSchedule; this.worldArchive = worldArchive; + this.transactionPool = transactionPool; this.blocks = blocks; this.scheduler = scheduler; } @@ -150,6 +155,7 @@ public class BlockchainSetupUtil { final GenesisState genesisState = GenesisState.fromJson(genesisJson, protocolSchedule); final MutableBlockchain blockchain = createInMemoryBlockchain(genesisState.getBlock()); final WorldStateArchive worldArchive = createInMemoryWorldStateArchive(); + final TransactionPool transactionPool = mock(TransactionPool.class); genesisState.writeStateTo(worldArchive.getMutable()); final ProtocolContext protocolContext = @@ -172,6 +178,7 @@ public class BlockchainSetupUtil { protocolContext, protocolSchedule, worldArchive, + transactionPool, blocks, scheduler); } catch (final IOException | URISyntaxException ex) { @@ -209,6 +216,10 @@ public class BlockchainSetupUtil { return scheduler; } + public TransactionPool getTransactionPool() { + return transactionPool; + } + private void importBlocks(final List blocks) { for (final Block block : blocks) { if (block.getHeader().getNumber() == BlockHeader.GENESIS_BLOCK_NUMBER) { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/EthProtocol.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/EthProtocol.java index 05239e3038..2d8dcde5d9 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/EthProtocol.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/EthProtocol.java @@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.eth; import org.hyperledger.besu.ethereum.eth.messages.EthPV62; import org.hyperledger.besu.ethereum.eth.messages.EthPV63; +import org.hyperledger.besu.ethereum.eth.messages.EthPV65; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Capability; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.SubProtocol; @@ -32,6 +33,8 @@ public class EthProtocol implements SubProtocol { public static final Capability ETH62 = Capability.create(NAME, EthVersion.V62); public static final Capability ETH63 = Capability.create(NAME, EthVersion.V63); public static final Capability ETH64 = Capability.create(NAME, EthVersion.V64); + public static final Capability ETH65 = Capability.create(NAME, EthVersion.V65); + private static final EthProtocol INSTANCE = new EthProtocol(); private static final List eth62Messages = @@ -53,6 +56,16 @@ public class EthProtocol implements SubProtocol { EthPV63.GET_NODE_DATA, EthPV63.NODE_DATA, EthPV63.GET_RECEIPTS, EthPV63.RECEIPTS)); } + private static final List eth65Messages = new ArrayList<>(eth63Messages); + + static { + eth65Messages.addAll( + Arrays.asList( + EthPV65.NEW_POOLED_TRANSACTION_HASHES, + EthPV65.GET_POOLED_TRANSACTIONS, + EthPV65.POOLED_TRANSACTIONS)); + } + @Override public String getName() { return NAME; @@ -65,6 +78,9 @@ public class EthProtocol implements SubProtocol { return 8; case EthVersion.V63: case EthVersion.V64: + case EthVersion + .V65: // same number of messages in the range, eth65 defines messages in the middle of the + // range defined by eth63. return 17; default: return 0; @@ -79,6 +95,8 @@ public class EthProtocol implements SubProtocol { case EthVersion.V63: case EthVersion.V64: return eth63Messages.contains(code); + case EthVersion.V65: + return eth65Messages.contains(code); default: return false; } @@ -103,6 +121,12 @@ public class EthProtocol implements SubProtocol { return "BlockBodies"; case EthPV62.NEW_BLOCK: return "NewBlock"; + case EthPV65.NEW_POOLED_TRANSACTION_HASHES: + return "NewPooledTransactionHashes"; + case EthPV65.GET_POOLED_TRANSACTIONS: + return "GetPooledTransactions"; + case EthPV65.POOLED_TRANSACTIONS: + return "PooledTransactions"; case EthPV63.GET_NODE_DATA: return "GetNodeData"; case EthPV63.NODE_DATA: @@ -124,5 +148,6 @@ public class EthProtocol implements SubProtocol { public static final int V62 = 62; public static final int V63 = 63; public static final int V64 = 64; + public static final int V65 = 65; } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/EthProtocolConfiguration.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/EthProtocolConfiguration.java index 31e815df2e..405c95dd3d 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/EthProtocolConfiguration.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/EthProtocolConfiguration.java @@ -26,21 +26,25 @@ public class EthProtocolConfiguration { public static final int DEFAULT_MAX_GET_BLOCK_BODIES = 128; public static final int DEFAULT_MAX_GET_RECEIPTS = 256; public static final int DEFAULT_MAX_GET_NODE_DATA = 384; + public static final int DEFAULT_MAX_GET_POOLED_TRANSACTIONS = 256; private final int maxGetBlockHeaders; private final int maxGetBlockBodies; private final int maxGetReceipts; private final int maxGetNodeData; + private final int maxGetPooledTransactions; public EthProtocolConfiguration( final int maxGetBlockHeaders, final int maxGetBlockBodies, final int maxGetReceipts, - final int maxGetNodeData) { + final int maxGetNodeData, + final int maxGetPooledTransactions) { this.maxGetBlockHeaders = maxGetBlockHeaders; this.maxGetBlockBodies = maxGetBlockBodies; this.maxGetReceipts = maxGetReceipts; this.maxGetNodeData = maxGetNodeData; + this.maxGetPooledTransactions = maxGetPooledTransactions; } public static EthProtocolConfiguration defaultConfig() { @@ -48,7 +52,8 @@ public class EthProtocolConfiguration { DEFAULT_MAX_GET_BLOCK_HEADERS, DEFAULT_MAX_GET_BLOCK_BODIES, DEFAULT_MAX_GET_RECEIPTS, - DEFAULT_MAX_GET_NODE_DATA); + DEFAULT_MAX_GET_NODE_DATA, + DEFAULT_MAX_GET_POOLED_TRANSACTIONS); } public static Builder builder() { @@ -71,6 +76,10 @@ public class EthProtocolConfiguration { return maxGetNodeData; } + public int getMaxGetPooledTransactions() { + return maxGetPooledTransactions; + } + @Override public boolean equals(final Object o) { if (this == o) { @@ -83,7 +92,8 @@ public class EthProtocolConfiguration { return maxGetBlockHeaders == that.maxGetBlockHeaders && maxGetBlockBodies == that.maxGetBlockBodies && maxGetReceipts == that.maxGetReceipts - && maxGetNodeData == that.maxGetNodeData; + && maxGetNodeData == that.maxGetNodeData + && maxGetPooledTransactions == that.maxGetPooledTransactions; } @Override @@ -98,6 +108,7 @@ public class EthProtocolConfiguration { .add("maxGetBlockBodies", maxGetBlockBodies) .add("maxGetReceipts", maxGetReceipts) .add("maxGetNodeData", maxGetNodeData) + .add("maxGetPooledTransactions", maxGetPooledTransactions) .toString(); } @@ -114,6 +125,9 @@ public class EthProtocolConfiguration { private PositiveNumber maxGetNodeData = PositiveNumber.fromInt(EthProtocolConfiguration.DEFAULT_MAX_GET_NODE_DATA); + private PositiveNumber maxGetPooledTransactions = + PositiveNumber.fromInt(EthProtocolConfiguration.DEFAULT_MAX_GET_POOLED_TRANSACTIONS); + public Builder maxGetBlockHeaders(final PositiveNumber maxGetBlockHeaders) { this.maxGetBlockHeaders = maxGetBlockHeaders; return this; @@ -134,12 +148,18 @@ public class EthProtocolConfiguration { return this; } + public Builder maxGetPooledTransactions(final PositiveNumber maxGetPooledTransactions) { + this.maxGetPooledTransactions = maxGetPooledTransactions; + return this; + } + public EthProtocolConfiguration build() { return new EthProtocolConfiguration( maxGetBlockHeaders.getValue(), maxGetBlockBodies.getValue(), maxGetReceipts.getValue(), - maxGetNodeData.getValue()); + maxGetNodeData.getValue(), + maxGetPooledTransactions.getValue()); } } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthPeer.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthPeer.java index 5bf484e017..043491eb57 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthPeer.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthPeer.java @@ -20,9 +20,11 @@ import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.Hash; import org.hyperledger.besu.ethereum.eth.messages.EthPV62; import org.hyperledger.besu.ethereum.eth.messages.EthPV63; +import org.hyperledger.besu.ethereum.eth.messages.EthPV65; import org.hyperledger.besu.ethereum.eth.messages.GetBlockBodiesMessage; import org.hyperledger.besu.ethereum.eth.messages.GetBlockHeadersMessage; import org.hyperledger.besu.ethereum.eth.messages.GetNodeDataMessage; +import org.hyperledger.besu.ethereum.eth.messages.GetPooledTransactionsMessage; import org.hyperledger.besu.ethereum.eth.messages.GetReceiptsMessage; import org.hyperledger.besu.ethereum.eth.peervalidation.PeerValidator; import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection; @@ -67,6 +69,7 @@ public class EthPeer { private final RequestManager bodiesRequestManager = new RequestManager(this); private final RequestManager receiptsRequestManager = new RequestManager(this); private final RequestManager nodeDataRequestManager = new RequestManager(this); + private final RequestManager pooledTransactionsRequestManager = new RequestManager(this); private final AtomicReference> onStatusesExchanged = new AtomicReference<>(); private final PeerReputation reputation = new PeerReputation(); @@ -154,6 +157,8 @@ public class EthPeer { return sendRequest(receiptsRequestManager, messageData); case EthPV63.GET_NODE_DATA: return sendRequest(nodeDataRequestManager, messageData); + case EthPV65.GET_POOLED_TRANSACTIONS: + return sendRequest(pooledTransactionsRequestManager, messageData); default: connection.sendForProtocol(protocolName, messageData); return null; @@ -201,6 +206,12 @@ public class EthPeer { return sendRequest(nodeDataRequestManager, message); } + public RequestManager.ResponseStream getPooledTransactions(final List hashes) + throws PeerNotConnected { + final GetPooledTransactionsMessage message = GetPooledTransactionsMessage.create(hashes); + return sendRequest(pooledTransactionsRequestManager, message); + } + boolean validateReceivedMessage(final EthMessage message) { checkArgument(message.getPeer().equals(this), "Mismatched message sent to peer for dispatch"); switch (message.getData().getCode()) { @@ -228,6 +239,12 @@ public class EthPeer { return false; } break; + case EthPV65.POOLED_TRANSACTIONS: + if (pooledTransactionsRequestManager.outstandingRequests() == 0) { + LOG.warn("Unsolicited pooling transactions received."); + return false; + } + break; default: // Nothing to do } @@ -258,6 +275,10 @@ public class EthPeer { reputation.resetTimeoutCount(EthPV63.GET_NODE_DATA); nodeDataRequestManager.dispatchResponse(message); break; + case EthPV65.POOLED_TRANSACTIONS: + reputation.resetTimeoutCount(EthPV65.GET_POOLED_TRANSACTIONS); + pooledTransactionsRequestManager.dispatchResponse(message); + break; default: // Nothing to do } @@ -272,6 +293,7 @@ public class EthPeer { bodiesRequestManager.close(); receiptsRequestManager.close(); nodeDataRequestManager.close(); + pooledTransactionsRequestManager.close(); } public void registerKnownBlock(final Hash hash) { @@ -344,7 +366,8 @@ public class EthPeer { return headersRequestManager.outstandingRequests() + bodiesRequestManager.outstandingRequests() + receiptsRequestManager.outstandingRequests() - + nodeDataRequestManager.outstandingRequests(); + + nodeDataRequestManager.outstandingRequests() + + pooledTransactionsRequestManager.outstandingRequests(); } public long getLastRequestTimestamp() { 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 94c35fbbd6..06b2f07a73 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 @@ -29,6 +29,7 @@ import org.hyperledger.besu.ethereum.eth.messages.StatusMessage; import org.hyperledger.besu.ethereum.eth.peervalidation.PeerValidator; import org.hyperledger.besu.ethereum.eth.peervalidation.PeerValidatorRunner; import org.hyperledger.besu.ethereum.eth.sync.BlockBroadcaster; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.p2p.network.ProtocolManager; import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection; import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection.PeerNotConnected; @@ -38,10 +39,8 @@ import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason; import org.hyperledger.besu.ethereum.rlp.RLPException; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; -import org.hyperledger.besu.plugin.services.MetricsSystem; import java.math.BigInteger; -import java.time.Clock; import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; @@ -54,9 +53,9 @@ import org.apache.logging.log4j.Logger; public class EthProtocolManager implements ProtocolManager, MinedBlockObserver { private static final Logger LOG = LogManager.getLogger(); private static final List FAST_SYNC_CAPS = - List.of(EthProtocol.ETH63, EthProtocol.ETH64); + List.of(EthProtocol.ETH63, EthProtocol.ETH64, EthProtocol.ETH65); private static final List FULL_SYNC_CAPS = - List.of(EthProtocol.ETH62, EthProtocol.ETH63, EthProtocol.ETH64); + List.of(EthProtocol.ETH62, EthProtocol.ETH63, EthProtocol.ETH64, EthProtocol.ETH65); private final EthScheduler scheduler; private final CountDownLatch shutdown; @@ -76,14 +75,16 @@ public class EthProtocolManager implements ProtocolManager, MinedBlockObserver { public EthProtocolManager( final Blockchain blockchain, - final WorldStateArchive worldStateArchive, final BigInteger networkId, + final WorldStateArchive worldStateArchive, + final TransactionPool transactionPool, + final EthProtocolConfiguration ethereumWireProtocolConfiguration, + final EthPeers ethPeers, + final EthMessages ethMessages, + final EthContext ethContext, final List peerValidators, final boolean fastSyncEnabled, final EthScheduler scheduler, - final EthProtocolConfiguration ethereumWireProtocolConfiguration, - final Clock clock, - final MetricsSystem metricsSystem, final ForkIdManager forkIdManager) { this.networkId = networkId; this.peerValidators = peerValidators; @@ -96,9 +97,9 @@ public class EthProtocolManager implements ProtocolManager, MinedBlockObserver { this.forkIdManager = forkIdManager; - ethPeers = new EthPeers(getSupportedProtocol(), clock, metricsSystem); - ethMessages = new EthMessages(); - ethContext = new EthContext(ethPeers, ethMessages, scheduler); + this.ethPeers = ethPeers; + this.ethMessages = ethMessages; + this.ethContext = ethContext; this.blockBroadcaster = new BlockBroadcaster(ethContext); @@ -108,58 +109,67 @@ public class EthProtocolManager implements ProtocolManager, MinedBlockObserver { } // Set up request handlers - new EthServer(blockchain, worldStateArchive, ethMessages, ethereumWireProtocolConfiguration); + new EthServer( + blockchain, + worldStateArchive, + transactionPool, + ethMessages, + ethereumWireProtocolConfiguration); } @VisibleForTesting public EthProtocolManager( final Blockchain blockchain, - final WorldStateArchive worldStateArchive, final BigInteger networkId, + final WorldStateArchive worldStateArchive, + final TransactionPool transactionPool, + final EthProtocolConfiguration ethereumWireProtocolConfiguration, + final EthPeers ethPeers, + final EthMessages ethMessages, + final EthContext ethContext, final List peerValidators, final boolean fastSyncEnabled, - final int syncWorkers, - final int txWorkers, - final int computationWorkers, - final Clock clock, - final MetricsSystem metricsSystem, - final EthProtocolConfiguration ethereumWireProtocolConfiguration) { + final EthScheduler scheduler) { this( blockchain, - worldStateArchive, networkId, + worldStateArchive, + transactionPool, + ethereumWireProtocolConfiguration, + ethPeers, + ethMessages, + ethContext, peerValidators, fastSyncEnabled, - new EthScheduler(syncWorkers, txWorkers, computationWorkers, metricsSystem), - ethereumWireProtocolConfiguration, - clock, - metricsSystem, + scheduler, new ForkIdManager(blockchain, Collections.emptyList())); } public EthProtocolManager( final Blockchain blockchain, - final WorldStateArchive worldStateArchive, final BigInteger networkId, + final WorldStateArchive worldStateArchive, + final TransactionPool transactionPool, + final EthProtocolConfiguration ethereumWireProtocolConfiguration, + final EthPeers ethPeers, + final EthMessages ethMessages, + final EthContext ethContext, final List peerValidators, final boolean fastSyncEnabled, - final int syncWorkers, - final int txWorkers, - final int computationWorkers, - final Clock clock, - final MetricsSystem metricsSystem, - final EthProtocolConfiguration ethereumWireProtocolConfiguration, + final EthScheduler scheduler, final List forks) { this( blockchain, - worldStateArchive, networkId, + worldStateArchive, + transactionPool, + ethereumWireProtocolConfiguration, + ethPeers, + ethMessages, + ethContext, peerValidators, fastSyncEnabled, - new EthScheduler(syncWorkers, txWorkers, computationWorkers, metricsSystem), - ethereumWireProtocolConfiguration, - clock, - metricsSystem, + scheduler, new ForkIdManager(blockchain, forks)); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthScheduler.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthScheduler.java index a65f8dc747..66a0b93614 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthScheduler.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthScheduler.java @@ -54,7 +54,7 @@ public class EthScheduler { protected final ExecutorService servicesExecutor; protected final ExecutorService computationExecutor; - private final Collection> serviceFutures = new ConcurrentLinkedDeque<>(); + private final Collection> pendingFutures = new ConcurrentLinkedDeque<>(); public EthScheduler( final int syncWorkerCount, @@ -120,21 +120,28 @@ public class EthScheduler { syncWorkerExecutor.execute(command); } + public CompletableFuture scheduleSyncWorkerTask(final EthTask task) { + final CompletableFuture syncFuture = task.runAsync(syncWorkerExecutor); + pendingFutures.add(syncFuture); + syncFuture.whenComplete((r, t) -> pendingFutures.remove(syncFuture)); + return syncFuture; + } + public void scheduleTxWorkerTask(final Runnable command) { txWorkerExecutor.execute(command); } public CompletableFuture scheduleServiceTask(final EthTask task) { final CompletableFuture serviceFuture = task.runAsync(servicesExecutor); - serviceFutures.add(serviceFuture); - serviceFuture.whenComplete((r, t) -> serviceFutures.remove(serviceFuture)); + pendingFutures.add(serviceFuture); + serviceFuture.whenComplete((r, t) -> pendingFutures.remove(serviceFuture)); return serviceFuture; } public CompletableFuture startPipeline(final Pipeline pipeline) { final CompletableFuture pipelineFuture = pipeline.start(servicesExecutor); - serviceFutures.add(pipelineFuture); - pipelineFuture.whenComplete((r, t) -> serviceFutures.remove(pipelineFuture)); + pendingFutures.add(pipelineFuture); + pipelineFuture.whenComplete((r, t) -> pendingFutures.remove(pipelineFuture)); return pipelineFuture; } @@ -226,7 +233,7 @@ public class EthScheduler { public void awaitStop() throws InterruptedException { shutdown.await(); - serviceFutures.forEach(future -> future.cancel(true)); + pendingFutures.forEach(future -> future.cancel(true)); if (!syncWorkerExecutor.awaitTermination(30, TimeUnit.SECONDS)) { LOG.error("{} worker executor did not shutdown cleanly.", this.getClass().getSimpleName()); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthServer.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthServer.java index 27f681392f..85a03dd9d2 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthServer.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthServer.java @@ -18,18 +18,23 @@ import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.messages.BlockBodiesMessage; import org.hyperledger.besu.ethereum.eth.messages.BlockHeadersMessage; import org.hyperledger.besu.ethereum.eth.messages.EthPV62; import org.hyperledger.besu.ethereum.eth.messages.EthPV63; +import org.hyperledger.besu.ethereum.eth.messages.EthPV65; import org.hyperledger.besu.ethereum.eth.messages.GetBlockBodiesMessage; import org.hyperledger.besu.ethereum.eth.messages.GetBlockHeadersMessage; import org.hyperledger.besu.ethereum.eth.messages.GetNodeDataMessage; +import org.hyperledger.besu.ethereum.eth.messages.GetPooledTransactionsMessage; import org.hyperledger.besu.ethereum.eth.messages.GetReceiptsMessage; import org.hyperledger.besu.ethereum.eth.messages.NodeDataMessage; +import org.hyperledger.besu.ethereum.eth.messages.PooledTransactionsMessage; import org.hyperledger.besu.ethereum.eth.messages.ReceiptsMessage; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; 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.DisconnectReason; @@ -52,16 +57,19 @@ class EthServer { private final Blockchain blockchain; private final WorldStateArchive worldStateArchive; + private final TransactionPool transactionPool; private final EthMessages ethMessages; private final EthProtocolConfiguration ethereumWireProtocolConfiguration; EthServer( final Blockchain blockchain, final WorldStateArchive worldStateArchive, + final TransactionPool transactionPool, final EthMessages ethMessages, final EthProtocolConfiguration ethereumWireProtocolConfiguration) { this.blockchain = blockchain; this.worldStateArchive = worldStateArchive; + this.transactionPool = transactionPool; this.ethMessages = ethMessages; this.ethereumWireProtocolConfiguration = ethereumWireProtocolConfiguration; this.setupListeners(); @@ -72,6 +80,7 @@ class EthServer { ethMessages.subscribe(EthPV62.GET_BLOCK_BODIES, this::handleGetBlockBodies); ethMessages.subscribe(EthPV63.GET_RECEIPTS, this::handleGetReceipts); ethMessages.subscribe(EthPV63.GET_NODE_DATA, this::handleGetNodeData); + ethMessages.subscribe(EthPV65.GET_POOLED_TRANSACTIONS, this::handleGetPooledTransactions); } private void handleGetBlockHeaders(final EthMessage message) { @@ -143,6 +152,26 @@ class EthServer { } } + private void handleGetPooledTransactions(final EthMessage message) { + LOG.trace("Responding to GET_POOLED_TRANSACTIONS request"); + try { + final MessageData response = + constructGetPooledTransactionsResponse( + transactionPool, + message.getData(), + ethereumWireProtocolConfiguration.getMaxGetPooledTransactions()); + message.getPeer().send(response); + } catch (final RLPException e) { + LOG.debug( + "Received malformed GET_POOLED_TRANSACTIONS message, disconnecting: {}", + message.getPeer(), + e); + message.getPeer().disconnect(DisconnectReason.BREACH_OF_PROTOCOL); + } catch (final PeerNotConnected peerNotConnected) { + // Peer disconnected before we could respond - nothing to do + } + } + static MessageData constructGetHeadersResponse( final Blockchain blockchain, final MessageData message, final int requestLimit) { final GetBlockHeadersMessage getHeaders = GetBlockHeadersMessage.readFrom(message); @@ -222,6 +251,28 @@ class EthServer { return ReceiptsMessage.create(receipts); } + static MessageData constructGetPooledTransactionsResponse( + final TransactionPool transactionPool, final MessageData message, final int requestLimit) { + final GetPooledTransactionsMessage getPooledTransactions = + GetPooledTransactionsMessage.readFrom(message); + final Iterable hashes = getPooledTransactions.pooledTransactions(); + + final List tx = new ArrayList<>(); + int count = 0; + for (final Hash hash : hashes) { + if (count >= requestLimit) { + break; + } + count++; + final Optional maybeTx = transactionPool.getTransactionByHash(hash); + if (maybeTx.isEmpty()) { + continue; + } + tx.add(maybeTx.get()); + } + return PooledTransactionsMessage.create(tx); + } + static MessageData constructGetNodeDataResponse( final WorldStateArchive worldStateArchive, final MessageData message, diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetPooledTransactionsFromPeerTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetPooledTransactionsFromPeerTask.java new file mode 100644 index 0000000000..cf8825f955 --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetPooledTransactionsFromPeerTask.java @@ -0,0 +1,92 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.manager.task; + +import static java.util.Collections.emptyList; + +import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.manager.EthPeer; +import org.hyperledger.besu.ethereum.eth.manager.PendingPeerRequest; +import org.hyperledger.besu.ethereum.eth.messages.EthPV65; +import org.hyperledger.besu.ethereum.eth.messages.PooledTransactionsMessage; +import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; +import org.hyperledger.besu.plugin.services.MetricsSystem; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class GetPooledTransactionsFromPeerTask extends AbstractPeerRequestTask> { + + private static final Logger LOG = LogManager.getLogger(); + + private final List hashes; + + private GetPooledTransactionsFromPeerTask( + final EthContext ethContext, final List hashes, final MetricsSystem metricsSystem) { + super(ethContext, EthPV65.GET_POOLED_TRANSACTIONS, metricsSystem); + this.hashes = new ArrayList<>(hashes); + } + + public static GetPooledTransactionsFromPeerTask forHashes( + final EthContext ethContext, final List hashes, final MetricsSystem metricsSystem) { + return new GetPooledTransactionsFromPeerTask(ethContext, hashes, metricsSystem); + } + + @Override + protected PendingPeerRequest sendRequest() { + return sendRequestToPeer( + peer -> { + LOG.debug("Requesting {} transaction pool entries from peer {}.", hashes.size(), peer); + return peer.getPooledTransactions(hashes); + }, + 0); + } + + @Override + protected Optional> processResponse( + final boolean streamClosed, final MessageData message, final EthPeer peer) { + if (streamClosed) { + // We don't record this as a useless response because it's impossible to know if a peer has + // the data we're requesting. + return Optional.of(emptyList()); + } + final PooledTransactionsMessage pooledTransactionsMessage = + PooledTransactionsMessage.readFrom(message); + final List tx = pooledTransactionsMessage.transactions(); + if (tx.size() > hashes.size()) { + // Can't be the response to our request + return Optional.empty(); + } + return mapNodeDataByHash(tx); + } + + private Optional> mapNodeDataByHash(final List transactions) { + final List result = new ArrayList<>(); + for (final Transaction tx : transactions) { + final Hash hash = tx.getHash(); + if (!hashes.contains(hash)) { + return Optional.empty(); + } + result.add(tx); + } + return Optional.of(result); + } +} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/EthPV65.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/EthPV65.java new file mode 100644 index 0000000000..763c0aafbe --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/EthPV65.java @@ -0,0 +1,28 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.messages; + +public final class EthPV65 { + + public static final int NEW_POOLED_TRANSACTION_HASHES = 0x08; + + public static final int GET_POOLED_TRANSACTIONS = 0x09; + + public static final int POOLED_TRANSACTIONS = 0x0A; + + private EthPV65() { + // Holder for constants only + } +} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/GetPooledTransactionsMessage.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/GetPooledTransactionsMessage.java new file mode 100644 index 0000000000..f6bd159e00 --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/GetPooledTransactionsMessage.java @@ -0,0 +1,69 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.messages; + +import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.ethereum.p2p.rlpx.wire.AbstractMessageData; +import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; + +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; + +public final class GetPooledTransactionsMessage extends AbstractMessageData { + + private static final int MESSAGE_CODE = EthPV65.GET_POOLED_TRANSACTIONS; + private List pooledTransactions; + + private GetPooledTransactionsMessage(final Bytes rlp) { + super(rlp); + } + + @Override + public int getCode() { + return MESSAGE_CODE; + } + + public static GetPooledTransactionsMessage create(final List pooledTransactions) { + List tx = pooledTransactions; + final BytesValueRLPOutput out = new BytesValueRLPOutput(); + out.writeList(tx, (h, w) -> w.writeBytes(h)); + return new GetPooledTransactionsMessage(out.encoded()); + } + + public static GetPooledTransactionsMessage readFrom(final MessageData message) { + if (message instanceof GetPooledTransactionsMessage) { + return (GetPooledTransactionsMessage) message; + } + final int code = message.getCode(); + if (code != MESSAGE_CODE) { + throw new IllegalArgumentException( + String.format( + "Message has code %d and thus is not a GetPooledTransactionsMessage.", code)); + } + + return new GetPooledTransactionsMessage(message.getData()); + } + + public List pooledTransactions() { + if (pooledTransactions == null) { + final BytesValueRLPInput in = new BytesValueRLPInput(getData(), false); + pooledTransactions = in.readList(rlp -> Hash.wrap(rlp.readBytes32())); + } + return pooledTransactions; + } +} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/LimitedNewPooledTransactionHashesMessages.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/LimitedNewPooledTransactionHashesMessages.java new file mode 100644 index 0000000000..cbe8b542dc --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/LimitedNewPooledTransactionHashesMessages.java @@ -0,0 +1,70 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.messages; + +import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; + +public final class LimitedNewPooledTransactionHashesMessages { + + static final int MAX_COUNT = 4096; + + private final NewPooledTransactionHashesMessage transactionsMessage; + private final List includedTransactions; + + public LimitedNewPooledTransactionHashesMessages( + final NewPooledTransactionHashesMessage transactionsMessage, + final List includedTransactions) { + this.transactionsMessage = transactionsMessage; + this.includedTransactions = includedTransactions; + } + + public static LimitedNewPooledTransactionHashesMessages createLimited( + final Iterable hashes) { + final List includedTransactions = new ArrayList<>(); + final BytesValueRLPOutput message = new BytesValueRLPOutput(); + int count = 0; + message.startList(); + for (final Hash txHash : hashes) { + final BytesValueRLPOutput encodedHashes = new BytesValueRLPOutput(); + encodedHashes.writeBytes(txHash); + Bytes encodedBytes = encodedHashes.encoded(); + + message.writeRLPUnsafe(encodedBytes); + includedTransactions.add(txHash); + // Check if last transaction to add to the message + count++; + if (count >= MAX_COUNT) { + break; + } + } + message.endList(); + return new LimitedNewPooledTransactionHashesMessages( + new NewPooledTransactionHashesMessage(message.encoded()), includedTransactions); + } + + public final NewPooledTransactionHashesMessage getTransactionsMessage() { + return transactionsMessage; + } + + public final List getIncludedTransactions() { + return includedTransactions; + } +} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/NewPooledTransactionHashesMessage.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/NewPooledTransactionHashesMessage.java new file mode 100644 index 0000000000..24bcfb6d21 --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/NewPooledTransactionHashesMessage.java @@ -0,0 +1,68 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.messages; + +import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.ethereum.p2p.rlpx.wire.AbstractMessageData; +import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; + +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; + +public final class NewPooledTransactionHashesMessage extends AbstractMessageData { + + private static final int MESSAGE_CODE = EthPV65.NEW_POOLED_TRANSACTION_HASHES; + private List pendingTransactions; + + NewPooledTransactionHashesMessage(final Bytes rlp) { + super(rlp); + } + + @Override + public int getCode() { + return MESSAGE_CODE; + } + + public static NewPooledTransactionHashesMessage create(final List pendingTransactions) { + final BytesValueRLPOutput out = new BytesValueRLPOutput(); + out.writeList(pendingTransactions, (h, w) -> w.writeBytes(h)); + return new NewPooledTransactionHashesMessage(out.encoded()); + } + + public static NewPooledTransactionHashesMessage readFrom(final MessageData message) { + if (message instanceof NewPooledTransactionHashesMessage) { + return (NewPooledTransactionHashesMessage) message; + } + final int code = message.getCode(); + if (code != MESSAGE_CODE) { + throw new IllegalArgumentException( + String.format( + "Message has code %d and thus is not a NewPooledTransactionHashesMessage.", code)); + } + + return new NewPooledTransactionHashesMessage(message.getData()); + } + + public List pendingTransactions() { + if (pendingTransactions == null) { + final BytesValueRLPInput in = new BytesValueRLPInput(getData(), false); + pendingTransactions = in.readList(rlp -> Hash.wrap(rlp.readBytes32())); + } + return pendingTransactions; + } +} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/PooledTransactionsMessage.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/PooledTransactionsMessage.java new file mode 100644 index 0000000000..d89c12e718 --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/PooledTransactionsMessage.java @@ -0,0 +1,68 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.messages; + +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.p2p.rlpx.wire.AbstractMessageData; +import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; + +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; + +public final class PooledTransactionsMessage extends AbstractMessageData { + + private static final int MESSAGE_CODE = EthPV65.POOLED_TRANSACTIONS; + private List pooledTransactions; + + private PooledTransactionsMessage(final Bytes rlp) { + super(rlp); + } + + @Override + public int getCode() { + return MESSAGE_CODE; + } + + public static PooledTransactionsMessage create(final List transactions) { + List tx = transactions; + final BytesValueRLPOutput out = new BytesValueRLPOutput(); + out.writeList(tx, Transaction::writeTo); + return new PooledTransactionsMessage(out.encoded()); + } + + public static PooledTransactionsMessage readFrom(final MessageData message) { + if (message instanceof PooledTransactionsMessage) { + return (PooledTransactionsMessage) message; + } + final int code = message.getCode(); + if (code != MESSAGE_CODE) { + throw new IllegalArgumentException( + String.format("Message has code %d and thus is not a PooledTransactionsMessage.", code)); + } + + return new PooledTransactionsMessage(message.getData()); + } + + public List transactions() { + if (pooledTransactions == null) { + final BytesValueRLPInput in = new BytesValueRLPInput(getData(), false); + pooledTransactions = in.readList(Transaction::readFrom); + } + return pooledTransactions; + } +} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PeerPendingTransactionTracker.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PeerPendingTransactionTracker.java new file mode 100644 index 0000000000..677cea2599 --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PeerPendingTransactionTracker.java @@ -0,0 +1,94 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.transactions; + +import static java.util.Collections.emptySet; + +import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.ethereum.eth.manager.EthPeer; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +public class PeerPendingTransactionTracker implements EthPeer.DisconnectCallback { + private static final int MAX_TRACKED_SEEN_TRANSACTIONS = 10_000; + private final Map> seenTransactions = new ConcurrentHashMap<>(); + private final Map> transactionsToSend = new ConcurrentHashMap<>(); + private final PendingTransactions pendingTransactions; + + public PeerPendingTransactionTracker(final PendingTransactions pendingTransactions) { + this.pendingTransactions = pendingTransactions; + } + + public synchronized void markTransactionsHashesAsSeen( + final EthPeer peer, final Collection transactions) { + final Set seenTransactionsForPeer = getOrCreateSeenTransactionsForPeer(peer); + transactions.stream().forEach(seenTransactionsForPeer::add); + } + + public synchronized void addToPeerSendQueue(final EthPeer peer, final Hash hash) { + if (!hasPeerSeenTransaction(peer, hash)) { + transactionsToSend.computeIfAbsent(peer, key -> createTransactionsSet()).add(hash); + } + } + + public Iterable getEthPeersWithUnsentTransactions() { + return transactionsToSend.keySet(); + } + + public synchronized Set claimTransactionsToSendToPeer(final EthPeer peer) { + final Set transactionsToSend = this.transactionsToSend.remove(peer); + if (transactionsToSend != null) { + markTransactionsHashesAsSeen( + peer, + transactionsToSend.stream() + .filter(h -> pendingTransactions.getTransactionByHash(h).isPresent()) + .collect(Collectors.toSet())); + return transactionsToSend; + } else { + return emptySet(); + } + } + + private Set getOrCreateSeenTransactionsForPeer(final EthPeer peer) { + return seenTransactions.computeIfAbsent(peer, key -> createTransactionsSet()); + } + + private boolean hasPeerSeenTransaction(final EthPeer peer, final Hash hash) { + final Set seenTransactionsForPeer = seenTransactions.get(peer); + return seenTransactionsForPeer != null && seenTransactionsForPeer.contains(hash); + } + + private Set createTransactionsSet() { + return Collections.newSetFromMap( + new LinkedHashMap(1 << 4, 0.75f, true) { + @Override + protected boolean removeEldestEntry(final Map.Entry eldest) { + return size() > MAX_TRACKED_SEEN_TRANSACTIONS; + } + }); + } + + @Override + public void onDisconnect(final EthPeer peer) { + seenTransactions.remove(peer); + transactionsToSend.remove(peer); + } +} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionSender.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionSender.java new file mode 100644 index 0000000000..5617521f8a --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionSender.java @@ -0,0 +1,50 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.transactions; + +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool.TransactionBatchAddedListener; + +class PendingTransactionSender implements TransactionBatchAddedListener { + + private final PeerPendingTransactionTracker transactionTracker; + private final PendingTransactionsMessageSender transactionsMessageSender; + private final EthContext ethContext; + + public PendingTransactionSender( + final PeerPendingTransactionTracker transactionTracker, + final PendingTransactionsMessageSender transactionsMessageSender, + final EthContext ethContext) { + this.transactionTracker = transactionTracker; + this.transactionsMessageSender = transactionsMessageSender; + this.ethContext = ethContext; + } + + @Override + public void onTransactionsAdded(final Iterable transactions) { + ethContext + .getEthPeers() + .streamAvailablePeers() + .forEach( + peer -> + transactions.forEach( + transaction -> + transactionTracker.addToPeerSendQueue(peer, transaction.getHash()))); + ethContext + .getScheduler() + .scheduleSyncWorkerTask(transactionsMessageSender::sendTransactionsToPeers); + } +} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactions.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactions.java index 075200991c..21a0b96cfa 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactions.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactions.java @@ -30,6 +30,7 @@ import java.time.Clock; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -37,6 +38,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.OptionalLong; +import java.util.Queue; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; @@ -44,6 +46,9 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.EvictingQueue; + /** * Holds the current set of pending transactions with the ability to iterate them based on priority * for mining or look-up by hash. @@ -55,6 +60,7 @@ public class PendingTransactions { private final int maxTransactionRetentionHours; private final Clock clock; + private final Queue newPooledHashes; private final Map pendingTransactions = new ConcurrentHashMap<>(); private final SortedSet prioritizedTransactions = new TreeSet<>( @@ -72,17 +78,20 @@ public class PendingTransactions { private final LabelledMetric transactionRemovedCounter; private final Counter localTransactionAddedCounter; private final Counter remoteTransactionAddedCounter; + private final Counter localTransactionHashesAddedCounter; private final long maxPendingTransactions; public PendingTransactions( final int maxTransactionRetentionHours, final int maxPendingTransactions, + final int maxPooledTransactionHashes, final Clock clock, final MetricsSystem metricsSystem) { this.maxTransactionRetentionHours = maxTransactionRetentionHours; this.maxPendingTransactions = maxPendingTransactions; this.clock = clock; + this.newPooledHashes = EvictingQueue.create(maxPooledTransactionHashes); final LabelledMetric transactionAddedCounter = metricsSystem.createLabelledCounter( BesuMetricCategory.TRANSACTION_POOL, @@ -91,6 +100,7 @@ public class PendingTransactions { "source"); localTransactionAddedCounter = transactionAddedCounter.labels("local"); remoteTransactionAddedCounter = transactionAddedCounter.labels("remote"); + localTransactionHashesAddedCounter = transactionAddedCounter.labels("pool"); transactionRemovedCounter = metricsSystem.createLabelledCounter( @@ -127,7 +137,19 @@ public class PendingTransactions { return transactionAdded; } - boolean addLocalTransaction(final Transaction transaction) { + boolean addTransactionHash(final Hash transactionHash) { + boolean hashAdded; + synchronized (newPooledHashes) { + hashAdded = newPooledHashes.add(transactionHash); + } + if (hashAdded) { + localTransactionHashesAddedCounter.inc(); + } + return hashAdded; + } + + @VisibleForTesting + public boolean addLocalTransaction(final Transaction transaction) { final boolean transactionAdded = addTransaction(new TransactionInfo(transaction, true, clock.instant())); if (transactionAdded) { @@ -220,6 +242,7 @@ public class PendingTransactions { } prioritizedTransactions.add(transactionInfo); pendingTransactions.put(transactionInfo.getHash(), transactionInfo); + tryEvictTransactionHash(transactionInfo.getHash()); if (pendingTransactions.size() > maxPendingTransactions) { final TransactionInfo toRemove = prioritizedTransactions.last(); @@ -340,6 +363,16 @@ public class PendingTransactions { } } + public void tryEvictTransactionHash(final Hash hash) { + synchronized (newPooledHashes) { + newPooledHashes.remove(hash); + } + } + + public Collection getNewPooledHashes() { + return newPooledHashes; + } + /** * Tracks the additional metadata associated with transactions to enable prioritization for mining * and deciding which transactions to drop when the transaction pool reaches its size limit. diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionsMessageHandler.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionsMessageHandler.java new file mode 100644 index 0000000000..db6270992c --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionsMessageHandler.java @@ -0,0 +1,52 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.transactions; + +import static java.time.Instant.now; + +import org.hyperledger.besu.ethereum.eth.manager.EthMessage; +import org.hyperledger.besu.ethereum.eth.manager.EthMessages; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; +import org.hyperledger.besu.ethereum.eth.messages.NewPooledTransactionHashesMessage; + +import java.time.Duration; +import java.time.Instant; + +class PendingTransactionsMessageHandler implements EthMessages.MessageCallback { + + private final PendingTransactionsMessageProcessor transactionsMessageProcessor; + private final EthScheduler scheduler; + private final Duration txMsgKeepAlive; + + public PendingTransactionsMessageHandler( + final EthScheduler scheduler, + final PendingTransactionsMessageProcessor transactionsMessageProcessor, + final int txMsgKeepAliveSeconds) { + this.scheduler = scheduler; + this.transactionsMessageProcessor = transactionsMessageProcessor; + this.txMsgKeepAlive = Duration.ofSeconds(txMsgKeepAliveSeconds); + } + + @Override + public void exec(final EthMessage message) { + final NewPooledTransactionHashesMessage transactionsMessage = + NewPooledTransactionHashesMessage.readFrom(message.getData()); + final Instant startedAt = now(); + scheduler.scheduleTxWorkerTask( + () -> + transactionsMessageProcessor.processNewPooledTransactionHashesMessage( + message.getPeer(), transactionsMessage, startedAt, txMsgKeepAlive)); + } +} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionsMessageProcessor.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionsMessageProcessor.java new file mode 100644 index 0000000000..3c26214213 --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionsMessageProcessor.java @@ -0,0 +1,120 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.transactions; + +import static java.time.Instant.now; +import static org.apache.logging.log4j.LogManager.getLogger; + +import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.manager.EthPeer; +import org.hyperledger.besu.ethereum.eth.manager.task.GetPooledTransactionsFromPeerTask; +import org.hyperledger.besu.ethereum.eth.messages.NewPooledTransactionHashesMessage; +import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason; +import org.hyperledger.besu.ethereum.rlp.RLPException; +import org.hyperledger.besu.metrics.RunnableCounter; +import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.metrics.Counter; + +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + +import org.apache.logging.log4j.Logger; + +class PendingTransactionsMessageProcessor { + + private static final int MAX_HASHES = 256; + private static final int SKIPPED_MESSAGES_LOGGING_THRESHOLD = 1000; + private static final Logger LOG = getLogger(); + private final PeerPendingTransactionTracker transactionTracker; + private final Counter totalSkippedTransactionsMessageCounter; + private final TransactionPool transactionPool; + private final EthContext ethContext; + private final MetricsSystem metricsSystem; + + PendingTransactionsMessageProcessor( + final PeerPendingTransactionTracker transactionTracker, + final TransactionPool transactionPool, + final Counter metricsCounter, + final EthContext ethContext, + final MetricsSystem metricsSystem) { + this.transactionTracker = transactionTracker; + this.transactionPool = transactionPool; + this.ethContext = ethContext; + this.metricsSystem = metricsSystem; + this.totalSkippedTransactionsMessageCounter = + new RunnableCounter( + metricsCounter, + () -> + LOG.warn( + "{} expired transaction messages have been skipped.", + SKIPPED_MESSAGES_LOGGING_THRESHOLD), + SKIPPED_MESSAGES_LOGGING_THRESHOLD); + } + + void processNewPooledTransactionHashesMessage( + final EthPeer peer, + final NewPooledTransactionHashesMessage transactionsMessage, + final Instant startedAt, + final Duration keepAlive) { + // Check if message not expired. + if (startedAt.plus(keepAlive).isAfter(now())) { + this.processNewPooledTransactionHashesMessage(peer, transactionsMessage); + } else { + totalSkippedTransactionsMessageCounter.inc(); + } + } + + private void processNewPooledTransactionHashesMessage( + final EthPeer peer, final NewPooledTransactionHashesMessage transactionsMessage) { + try { + LOG.trace("Received pooled transaction hashes message from {}", peer); + + final List pendingHashes = transactionsMessage.pendingTransactions(); + transactionTracker.markTransactionsHashesAsSeen(peer, pendingHashes); + List toRequest = new ArrayList<>(); + for (Hash hash : pendingHashes) { + if (transactionPool.addTransactionHashes(hash)) { + toRequest.add(hash); + } + } + while (!toRequest.isEmpty()) { + List messageHashes = toRequest.subList(0, Math.min(toRequest.size(), MAX_HASHES)); + GetPooledTransactionsFromPeerTask task = + GetPooledTransactionsFromPeerTask.forHashes(ethContext, messageHashes, metricsSystem); + task.assignPeer(peer); + ethContext + .getScheduler() + .scheduleSyncWorkerTask(task) + .thenAccept( + result -> { + List txs = result.getResult(); + transactionPool.addRemoteTransactions(txs); + }); + + toRequest.removeAll(messageHashes); + } + } catch (final RLPException ex) { + if (peer != null) { + LOG.debug( + "Malformed pooled transaction hashes message received, disconnecting: {}", peer, ex); + peer.disconnect(DisconnectReason.BREACH_OF_PROTOCOL); + } + } + } +} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionsMessageSender.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionsMessageSender.java new file mode 100644 index 0000000000..8e1ef7d9ee --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionsMessageSender.java @@ -0,0 +1,52 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.transactions; + +import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.ethereum.eth.manager.EthPeer; +import org.hyperledger.besu.ethereum.eth.messages.LimitedNewPooledTransactionHashesMessages; +import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection.PeerNotConnected; + +import java.util.Set; +import java.util.stream.StreamSupport; + +class PendingTransactionsMessageSender { + + private final PeerPendingTransactionTracker transactionTracker; + + public PendingTransactionsMessageSender(final PeerPendingTransactionTracker transactionTracker) { + this.transactionTracker = transactionTracker; + } + + public void sendTransactionsToPeers() { + StreamSupport.stream(transactionTracker.getEthPeersWithUnsentTransactions().spliterator(), true) + .parallel() + .forEach(this::sendTransactionsToPeer); + } + + private void sendTransactionsToPeer(final EthPeer peer) { + final Set allTxToSend = transactionTracker.claimTransactionsToSendToPeer(peer); + while (!allTxToSend.isEmpty()) { + final LimitedNewPooledTransactionHashesMessages limitedTransactionsMessages = + LimitedNewPooledTransactionHashesMessages.createLimited(allTxToSend); + allTxToSend.removeAll(limitedTransactionsMessages.getIncludedTransactions()); + try { + peer.send(limitedTransactionsMessages.getTransactionsMessage()); + } catch (final PeerNotConnected e) { + return; + } + } + } +} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPool.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPool.java index f6978a2fe4..2818aecdfd 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPool.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPool.java @@ -25,6 +25,7 @@ import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Account; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.Hash; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.Wei; import org.hyperledger.besu.ethereum.eth.manager.EthContext; @@ -43,6 +44,7 @@ import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import org.apache.logging.log4j.Logger; @@ -65,27 +67,33 @@ public class TransactionPool implements BlockAddedObserver { private final ProtocolSchedule protocolSchedule; private final ProtocolContext protocolContext; private final TransactionBatchAddedListener transactionBatchAddedListener; + private final TransactionBatchAddedListener pendingTransactionBatchAddedListener; private final SyncState syncState; private final Wei minTransactionGasPrice; private final LabelledMetric duplicateTransactionCounter; private final PeerTransactionTracker peerTransactionTracker; + private final PeerPendingTransactionTracker peerPendingTransactionTracker; public TransactionPool( final PendingTransactions pendingTransactions, final ProtocolSchedule protocolSchedule, final ProtocolContext protocolContext, final TransactionBatchAddedListener transactionBatchAddedListener, + final TransactionBatchAddedListener pendingTransactionBatchAddedListener, final SyncState syncState, final EthContext ethContext, final PeerTransactionTracker peerTransactionTracker, + final PeerPendingTransactionTracker peerPendingTransactionTracker, final Wei minTransactionGasPrice, final MetricsSystem metricsSystem) { this.pendingTransactions = pendingTransactions; this.protocolSchedule = protocolSchedule; this.protocolContext = protocolContext; this.transactionBatchAddedListener = transactionBatchAddedListener; + this.pendingTransactionBatchAddedListener = pendingTransactionBatchAddedListener; this.syncState = syncState; this.peerTransactionTracker = peerTransactionTracker; + this.peerPendingTransactionTracker = peerPendingTransactionTracker; this.minTransactionGasPrice = minTransactionGasPrice; duplicateTransactionCounter = @@ -103,12 +111,24 @@ public class TransactionPool implements BlockAddedObserver { for (final Transaction transaction : localTransactions) { peerTransactionTracker.addToPeerSendQueue(peer, transaction); } + final Collection hashes = getNewPooledHashes(); + for (final Hash hash : hashes) { + peerPendingTransactionTracker.addToPeerSendQueue(peer, hash); + } } public List getLocalTransactions() { return pendingTransactions.getLocalTransactions(); } + public Collection getNewPooledHashes() { + return pendingTransactions.getNewPooledHashes(); + } + + public boolean addTransactionHashes(final Hash transactionHash) { + return pendingTransactions.addTransactionHash(transactionHash); + } + public ValidationResult addLocalTransaction( final Transaction transaction) { if (transaction.getGasPrice().compareTo(minTransactionGasPrice) < 0) { @@ -121,7 +141,9 @@ public class TransactionPool implements BlockAddedObserver { () -> { final boolean added = pendingTransactions.addLocalTransaction(transaction); if (added) { - transactionBatchAddedListener.onTransactionsAdded(singletonList(transaction)); + Collection txs = singletonList(transaction); + transactionBatchAddedListener.onTransactionsAdded(txs); + pendingTransactionBatchAddedListener.onTransactionsAdded(txs); } else { duplicateTransactionCounter.labels(LOCAL).inc(); } @@ -135,6 +157,7 @@ public class TransactionPool implements BlockAddedObserver { } final Set addedTransactions = new HashSet<>(); for (final Transaction transaction : transactions) { + pendingTransactions.tryEvictTransactionHash(transaction.getHash()); if (pendingTransactions.containsTransaction(transaction.getHash())) { // We already have this transaction, don't even validate it. duplicateTransactionCounter.labels(REMOTE).inc(); @@ -226,6 +249,10 @@ public class TransactionPool implements BlockAddedObserver { .orElseGet(() -> ValidationResult.invalid(CHAIN_HEAD_WORLD_STATE_NOT_AVAILABLE)); } + public Optional getTransactionByHash(final Hash hash) { + return pendingTransactions.getTransactionByHash(hash); + } + private BlockHeader getChainHeadBlockHeader() { final MutableBlockchain blockchain = protocolContext.getBlockchain(); return blockchain.getBlockHeader(blockchain.getChainHeadHash()).get(); 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 5843c63155..a44853b8b2 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 @@ -19,17 +19,21 @@ import java.util.Objects; public class TransactionPoolConfiguration { public static final int DEFAULT_TX_MSG_KEEP_ALIVE = 60; public static final int MAX_PENDING_TRANSACTIONS = 4096; + public static final int MAX_PENDING_TRANSACTIONS_HASHES = 4096; public static final int DEFAULT_TX_RETENTION_HOURS = 13; private final int txPoolMaxSize; + private final int pooledTransactionHashesSize; private final int pendingTxRetentionPeriod; private final int txMessageKeepAliveSeconds; public TransactionPoolConfiguration( final int txPoolMaxSize, + final int pooledTransactionHashesSize, final int pendingTxRetentionPeriod, final int txMessageKeepAliveSeconds) { this.txPoolMaxSize = txPoolMaxSize; + this.pooledTransactionHashesSize = pooledTransactionHashesSize; this.pendingTxRetentionPeriod = pendingTxRetentionPeriod; this.txMessageKeepAliveSeconds = txMessageKeepAliveSeconds; } @@ -38,6 +42,10 @@ public class TransactionPoolConfiguration { return txPoolMaxSize; } + public int getPooledTransactionHashesSize() { + return pooledTransactionHashesSize; + } + public int getPendingTxRetentionPeriod() { return pendingTxRetentionPeriod; } @@ -85,12 +93,18 @@ public class TransactionPoolConfiguration { private int txPoolMaxSize = MAX_PENDING_TRANSACTIONS; private int pendingTxRetentionPeriod = DEFAULT_TX_RETENTION_HOURS; private Integer txMessageKeepAliveSeconds = DEFAULT_TX_MSG_KEEP_ALIVE; + private int pooledTransactionHashesSize = MAX_PENDING_TRANSACTIONS_HASHES; public Builder txPoolMaxSize(final int txPoolMaxSize) { this.txPoolMaxSize = txPoolMaxSize; return this; } + public Builder pooledTransactionHashesSize(final int pooledTransactionHashesSize) { + this.pooledTransactionHashesSize = pooledTransactionHashesSize; + return this; + } + public Builder pendingTxRetentionPeriod(final int pendingTxRetentionPeriod) { this.pendingTxRetentionPeriod = pendingTxRetentionPeriod; return this; @@ -103,7 +117,10 @@ public class TransactionPoolConfiguration { public TransactionPoolConfiguration build() { return new TransactionPoolConfiguration( - txPoolMaxSize, pendingTxRetentionPeriod, txMessageKeepAliveSeconds); + txPoolMaxSize, + pooledTransactionHashesSize, + pendingTxRetentionPeriod, + txMessageKeepAliveSeconds); } } } 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 57430a6abf..3d1393b5b6 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 @@ -18,6 +18,7 @@ import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.core.Wei; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.messages.EthPV62; +import org.hyperledger.besu.ethereum.eth.messages.EthPV65; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.metrics.BesuMetricCategory; @@ -41,22 +42,30 @@ public class TransactionPoolFactory { new PendingTransactions( transactionPoolConfiguration.getPendingTxRetentionPeriod(), transactionPoolConfiguration.getTxPoolMaxSize(), + transactionPoolConfiguration.getPooledTransactionHashesSize(), clock, metricsSystem); final PeerTransactionTracker transactionTracker = new PeerTransactionTracker(); final TransactionsMessageSender transactionsMessageSender = new TransactionsMessageSender(transactionTracker); + final PeerPendingTransactionTracker pendingTransactionTracker = + new PeerPendingTransactionTracker(pendingTransactions); + final PendingTransactionsMessageSender pendingTransactionsMessageSender = + new PendingTransactionsMessageSender(pendingTransactionTracker); final TransactionPool transactionPool = new TransactionPool( pendingTransactions, protocolSchedule, protocolContext, new TransactionSender(transactionTracker, transactionsMessageSender, ethContext), + new PendingTransactionSender( + pendingTransactionTracker, pendingTransactionsMessageSender, ethContext), syncState, ethContext, transactionTracker, + pendingTransactionTracker, minTransactionGasPrice, metricsSystem); @@ -71,8 +80,23 @@ public class TransactionPoolFactory { "transactions_messages_skipped_total", "Total number of transactions messages skipped by the processor.")), transactionPoolConfiguration.getTxMessageKeepAliveSeconds()); - + final PendingTransactionsMessageHandler pooledTransactionsMessageHandler = + new PendingTransactionsMessageHandler( + ethContext.getScheduler(), + new PendingTransactionsMessageProcessor( + pendingTransactionTracker, + transactionPool, + metricsSystem.createCounter( + BesuMetricCategory.TRANSACTION_POOL, + "pending_transactions_messages_skipped_total", + "Total number of pending transactions messages skipped by the processor."), + ethContext, + metricsSystem), + transactionPoolConfiguration.getTxMessageKeepAliveSeconds()); ethContext.getEthMessages().subscribe(EthPV62.TRANSACTIONS, transactionsMessageHandler); + ethContext + .getEthMessages() + .subscribe(EthPV65.NEW_POOLED_TRANSACTION_HASHES, pooledTransactionsMessageHandler); protocolContext.getBlockchain().observeBlockAdded(transactionPool); ethContext.getEthPeers().subscribeDisconnect(transactionTracker); return transactionPool; 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 e9666a486b..6491e74d46 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 @@ -53,6 +53,7 @@ import org.hyperledger.besu.ethereum.eth.messages.ReceiptsMessage; import org.hyperledger.besu.ethereum.eth.messages.StatusMessage; import org.hyperledger.besu.ethereum.eth.messages.TransactionsMessage; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolFactory; import org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSchedule; @@ -94,6 +95,7 @@ import org.mockito.ArgumentCaptor; public final class EthProtocolManagerTest { private static Blockchain blockchain; + private static TransactionPool transactionPool; private static ProtocolSchedule protocolSchedule; private static BlockDataGenerator gen; private static ProtocolContext protocolContext; @@ -105,6 +107,7 @@ public final class EthProtocolManagerTest { final BlockchainSetupUtil blockchainSetupUtil = BlockchainSetupUtil.forTesting(); blockchainSetupUtil.importAllBlocks(); blockchain = blockchainSetupUtil.getBlockchain(); + transactionPool = blockchainSetupUtil.getTransactionPool(); protocolSchedule = blockchainSetupUtil.getProtocolSchedule(); protocolContext = blockchainSetupUtil.getProtocolContext(); assert (blockchainSetupUtil.getMaxBlockNumber() >= 20L); @@ -113,17 +116,11 @@ public final class EthProtocolManagerTest { @Test public void disconnectOnUnsolicitedMessage() { try (final EthProtocolManager ethManager = - new EthProtocolManager( + EthProtocolManagerTestUtil.create( blockchain, + () -> false, protocolContext.getWorldStateArchive(), - BigInteger.ONE, - Collections.emptyList(), - true, - 1, - 1, - 1, - TestClock.fixed(), - new NoOpMetricsSystem(), + transactionPool, EthProtocolConfiguration.defaultConfig())) { final MessageData messageData = BlockHeadersMessage.create(Collections.singletonList(blockchain.getBlockHeader(1).get())); @@ -136,17 +133,11 @@ public final class EthProtocolManagerTest { @Test public void disconnectOnFailureToSendStatusMessage() { try (final EthProtocolManager ethManager = - new EthProtocolManager( + EthProtocolManagerTestUtil.create( blockchain, + () -> false, protocolContext.getWorldStateArchive(), - BigInteger.ONE, - Collections.emptyList(), - true, - 1, - 1, - 1, - TestClock.fixed(), - new NoOpMetricsSystem(), + transactionPool, EthProtocolConfiguration.defaultConfig())) { final MessageData messageData = BlockHeadersMessage.create(Collections.singletonList(blockchain.getBlockHeader(1).get())); @@ -160,17 +151,11 @@ public final class EthProtocolManagerTest { @Test public void disconnectOnWrongChainId() { try (final EthProtocolManager ethManager = - new EthProtocolManager( + EthProtocolManagerTestUtil.create( blockchain, + () -> false, protocolContext.getWorldStateArchive(), - BigInteger.ONE, - Collections.emptyList(), - true, - 1, - 1, - 1, - TestClock.fixed(), - new NoOpMetricsSystem(), + transactionPool, EthProtocolConfiguration.defaultConfig())) { final MessageData messageData = BlockHeadersMessage.create(Collections.singletonList(blockchain.getBlockHeader(1).get())); @@ -195,17 +180,11 @@ public final class EthProtocolManagerTest { @Test public void disconnectOnWrongGenesisHash() { try (final EthProtocolManager ethManager = - new EthProtocolManager( + EthProtocolManagerTestUtil.create( blockchain, + () -> false, protocolContext.getWorldStateArchive(), - BigInteger.ONE, - Collections.emptyList(), - true, - 1, - 1, - 1, - TestClock.fixed(), - new NoOpMetricsSystem(), + transactionPool, EthProtocolConfiguration.defaultConfig())) { final MessageData messageData = BlockHeadersMessage.create(Collections.singletonList(blockchain.getBlockHeader(1).get())); @@ -230,17 +209,11 @@ public final class EthProtocolManagerTest { @Test(expected = ConditionTimeoutException.class) public void doNotDisconnectOnValidMessage() { try (final EthProtocolManager ethManager = - new EthProtocolManager( + EthProtocolManagerTestUtil.create( blockchain, + () -> false, protocolContext.getWorldStateArchive(), - BigInteger.ONE, - Collections.emptyList(), - true, - 1, - 1, - 1, - TestClock.fixed(), - new NoOpMetricsSystem(), + transactionPool, EthProtocolConfiguration.defaultConfig())) { final MessageData messageData = GetBlockBodiesMessage.create(Collections.singletonList(gen.hash())); @@ -257,17 +230,11 @@ public final class EthProtocolManagerTest { public void respondToGetHeaders() throws ExecutionException, InterruptedException { final CompletableFuture done = new CompletableFuture<>(); try (final EthProtocolManager ethManager = - new EthProtocolManager( + EthProtocolManagerTestUtil.create( blockchain, + () -> false, protocolContext.getWorldStateArchive(), - BigInteger.ONE, - Collections.emptyList(), - true, - 1, - 1, - 1, - TestClock.fixed(), - new NoOpMetricsSystem(), + transactionPool, EthProtocolConfiguration.defaultConfig())) { final long startBlock = 5L; final int blockCount = 5; @@ -300,18 +267,12 @@ public final class EthProtocolManagerTest { final CompletableFuture done = new CompletableFuture<>(); final int limit = 5; try (final EthProtocolManager ethManager = - new EthProtocolManager( + EthProtocolManagerTestUtil.create( blockchain, + () -> false, protocolContext.getWorldStateArchive(), - BigInteger.ONE, - Collections.emptyList(), - true, - 1, - 1, - 1, - TestClock.fixed(), - new NoOpMetricsSystem(), - new EthProtocolConfiguration(limit, limit, limit, limit))) { + transactionPool, + new EthProtocolConfiguration(limit, limit, limit, limit, limit))) { final long startBlock = 5L; final int blockCount = 10; final MessageData messageData = @@ -342,18 +303,13 @@ public final class EthProtocolManagerTest { public void respondToGetHeadersReversed() throws ExecutionException, InterruptedException { final CompletableFuture done = new CompletableFuture<>(); try (final EthProtocolManager ethManager = - new EthProtocolManager( + EthProtocolManagerTestUtil.create( blockchain, + () -> false, protocolContext.getWorldStateArchive(), - BigInteger.ONE, - Collections.emptyList(), - true, - 1, - 1, - 1, - TestClock.fixed(), - new NoOpMetricsSystem(), + transactionPool, EthProtocolConfiguration.defaultConfig())) { + final long endBlock = 10L; final int blockCount = 5; final MessageData messageData = GetBlockHeadersMessage.create(endBlock, blockCount, 0, true); @@ -383,18 +339,13 @@ public final class EthProtocolManagerTest { public void respondToGetHeadersWithSkip() throws ExecutionException, InterruptedException { final CompletableFuture done = new CompletableFuture<>(); try (final EthProtocolManager ethManager = - new EthProtocolManager( + EthProtocolManagerTestUtil.create( blockchain, + () -> false, protocolContext.getWorldStateArchive(), - BigInteger.ONE, - Collections.emptyList(), - true, - 1, - 1, - 1, - TestClock.fixed(), - new NoOpMetricsSystem(), + transactionPool, EthProtocolConfiguration.defaultConfig())) { + final long startBlock = 5L; final int blockCount = 5; final int skip = 1; @@ -427,18 +378,13 @@ public final class EthProtocolManagerTest { throws ExecutionException, InterruptedException { final CompletableFuture done = new CompletableFuture<>(); try (final EthProtocolManager ethManager = - new EthProtocolManager( + EthProtocolManagerTestUtil.create( blockchain, + () -> false, protocolContext.getWorldStateArchive(), - BigInteger.ONE, - Collections.emptyList(), - true, - 1, - 1, - 1, - TestClock.fixed(), - new NoOpMetricsSystem(), + transactionPool, EthProtocolConfiguration.defaultConfig())) { + final long endBlock = 10L; final int blockCount = 5; final int skip = 1; @@ -492,18 +438,13 @@ public final class EthProtocolManagerTest { public void respondToGetHeadersPartial() throws ExecutionException, InterruptedException { final CompletableFuture done = new CompletableFuture<>(); try (final EthProtocolManager ethManager = - new EthProtocolManager( + EthProtocolManagerTestUtil.create( blockchain, + () -> false, protocolContext.getWorldStateArchive(), - BigInteger.ONE, - Collections.emptyList(), - true, - 1, - 1, - 1, - TestClock.fixed(), - new NoOpMetricsSystem(), + transactionPool, EthProtocolConfiguration.defaultConfig())) { + final long startBlock = blockchain.getChainHeadBlockNumber() - 1L; final int blockCount = 5; final MessageData messageData = @@ -534,18 +475,13 @@ public final class EthProtocolManagerTest { public void respondToGetHeadersEmpty() throws ExecutionException, InterruptedException { final CompletableFuture done = new CompletableFuture<>(); try (final EthProtocolManager ethManager = - new EthProtocolManager( + EthProtocolManagerTestUtil.create( blockchain, + () -> false, protocolContext.getWorldStateArchive(), - BigInteger.ONE, - Collections.emptyList(), - true, - 1, - 1, - 1, - TestClock.fixed(), - new NoOpMetricsSystem(), + transactionPool, EthProtocolConfiguration.defaultConfig())) { + final long startBlock = blockchain.getChainHeadBlockNumber() + 1; final int blockCount = 5; final MessageData messageData = @@ -573,18 +509,13 @@ public final class EthProtocolManagerTest { public void respondToGetBodies() throws ExecutionException, InterruptedException { final CompletableFuture done = new CompletableFuture<>(); try (final EthProtocolManager ethManager = - new EthProtocolManager( + EthProtocolManagerTestUtil.create( blockchain, + () -> false, protocolContext.getWorldStateArchive(), - BigInteger.ONE, - Collections.emptyList(), - true, - 1, - 1, - 1, - TestClock.fixed(), - new NoOpMetricsSystem(), + transactionPool, EthProtocolConfiguration.defaultConfig())) { + // Setup blocks query final long startBlock = blockchain.getChainHeadBlockNumber() - 5; final int blockCount = 2; @@ -628,18 +559,12 @@ public final class EthProtocolManagerTest { final CompletableFuture done = new CompletableFuture<>(); final int limit = 5; try (final EthProtocolManager ethManager = - new EthProtocolManager( + EthProtocolManagerTestUtil.create( blockchain, + () -> false, protocolContext.getWorldStateArchive(), - BigInteger.ONE, - Collections.emptyList(), - true, - 1, - 1, - 1, - TestClock.fixed(), - new NoOpMetricsSystem(), - new EthProtocolConfiguration(limit, limit, limit, limit))) { + transactionPool, + new EthProtocolConfiguration(limit, limit, limit, limit, limit))) { // Setup blocks query final int blockCount = 10; final long startBlock = blockchain.getChainHeadBlockNumber() - blockCount; @@ -682,17 +607,11 @@ public final class EthProtocolManagerTest { public void respondToGetBodiesPartial() throws ExecutionException, InterruptedException { final CompletableFuture done = new CompletableFuture<>(); try (final EthProtocolManager ethManager = - new EthProtocolManager( + EthProtocolManagerTestUtil.create( blockchain, + () -> false, protocolContext.getWorldStateArchive(), - BigInteger.ONE, - Collections.emptyList(), - true, - 1, - 1, - 1, - TestClock.fixed(), - new NoOpMetricsSystem(), + transactionPool, EthProtocolConfiguration.defaultConfig())) { // Setup blocks query final long expectedBlockNumber = blockchain.getChainHeadBlockNumber() - 1; @@ -730,17 +649,11 @@ public final class EthProtocolManagerTest { public void respondToGetReceipts() throws ExecutionException, InterruptedException { final CompletableFuture done = new CompletableFuture<>(); try (final EthProtocolManager ethManager = - new EthProtocolManager( + EthProtocolManagerTestUtil.create( blockchain, + () -> false, protocolContext.getWorldStateArchive(), - BigInteger.ONE, - Collections.emptyList(), - true, - 1, - 1, - 1, - TestClock.fixed(), - new NoOpMetricsSystem(), + transactionPool, EthProtocolConfiguration.defaultConfig())) { // Setup blocks query final long startBlock = blockchain.getChainHeadBlockNumber() - 5; @@ -784,18 +697,12 @@ public final class EthProtocolManagerTest { final CompletableFuture done = new CompletableFuture<>(); final int limit = 5; try (final EthProtocolManager ethManager = - new EthProtocolManager( + EthProtocolManagerTestUtil.create( blockchain, + () -> false, protocolContext.getWorldStateArchive(), - BigInteger.ONE, - Collections.emptyList(), - true, - 1, - 1, - 1, - TestClock.fixed(), - new NoOpMetricsSystem(), - new EthProtocolConfiguration(limit, limit, limit, limit))) { + transactionPool, + new EthProtocolConfiguration(limit, limit, limit, limit, limit))) { // Setup blocks query final int blockCount = 10; final long startBlock = blockchain.getChainHeadBlockNumber() - blockCount; @@ -837,17 +744,11 @@ public final class EthProtocolManagerTest { public void respondToGetReceiptsPartial() throws ExecutionException, InterruptedException { final CompletableFuture done = new CompletableFuture<>(); try (final EthProtocolManager ethManager = - new EthProtocolManager( + EthProtocolManagerTestUtil.create( blockchain, + () -> false, protocolContext.getWorldStateArchive(), - BigInteger.ONE, - Collections.emptyList(), - true, - 1, - 1, - 1, - TestClock.fixed(), - new NoOpMetricsSystem(), + transactionPool, EthProtocolConfiguration.defaultConfig())) { // Setup blocks query final long blockNumber = blockchain.getChainHeadBlockNumber() - 5; @@ -887,17 +788,11 @@ public final class EthProtocolManagerTest { final WorldStateArchive worldStateArchive = protocolContext.getWorldStateArchive(); try (final EthProtocolManager ethManager = - new EthProtocolManager( + EthProtocolManagerTestUtil.create( blockchain, - worldStateArchive, - BigInteger.ONE, - Collections.emptyList(), - true, - 1, - 1, - 1, - TestClock.fixed(), - new NoOpMetricsSystem(), + () -> false, + protocolContext.getWorldStateArchive(), + transactionPool, EthProtocolConfiguration.defaultConfig())) { // Setup node data query @@ -939,63 +834,59 @@ public final class EthProtocolManagerTest { @Test public void newBlockMinedSendsNewBlockMessageToAllPeers() { - final EthProtocolManager ethManager = - new EthProtocolManager( + try (final EthProtocolManager ethManager = + EthProtocolManagerTestUtil.create( blockchain, + () -> false, protocolContext.getWorldStateArchive(), - BigInteger.ONE, - Collections.emptyList(), - true, - 1, - 1, - 1, - TestClock.fixed(), - new NoOpMetricsSystem(), - EthProtocolConfiguration.defaultConfig()); - - // Define handler to validate response - final PeerSendHandler onSend = mock(PeerSendHandler.class); - final List peers = Lists.newArrayList(); - - final int PEER_COUNT = 5; - for (int i = 0; i < PEER_COUNT; i++) { - peers.add(setupPeer(ethManager, onSend)); - } + transactionPool, + EthProtocolConfiguration.defaultConfig())) { + // Define handler to validate response + final PeerSendHandler onSend = mock(PeerSendHandler.class); + final List peers = Lists.newArrayList(); - final Hash chainHeadHash = blockchain.getChainHeadHash(); - final Block minedBlock = - new Block( - blockchain.getBlockHeader(chainHeadHash).get(), - blockchain.getBlockBody(chainHeadHash).get()); + final int PEER_COUNT = 5; + for (int i = 0; i < PEER_COUNT; i++) { + peers.add(setupPeer(ethManager, onSend)); + } - final Difficulty expectedTotalDifficulty = blockchain.getChainHead().getTotalDifficulty(); + final Hash chainHeadHash = blockchain.getChainHeadHash(); + final Block minedBlock = + new Block( + blockchain.getBlockHeader(chainHeadHash).get(), + blockchain.getBlockBody(chainHeadHash).get()); - reset(onSend); + final Difficulty expectedTotalDifficulty = blockchain.getChainHead().getTotalDifficulty(); - ethManager.blockMined(minedBlock); + reset(onSend); - final ArgumentCaptor messageSentCaptor = - ArgumentCaptor.forClass(NewBlockMessage.class); - final ArgumentCaptor receivingPeerCaptor = - ArgumentCaptor.forClass(PeerConnection.class); - final ArgumentCaptor capabilityCaptor = ArgumentCaptor.forClass(Capability.class); + ethManager.blockMined(minedBlock); - verify(onSend, times(PEER_COUNT)) - .exec( - capabilityCaptor.capture(), messageSentCaptor.capture(), receivingPeerCaptor.capture()); + final ArgumentCaptor messageSentCaptor = + ArgumentCaptor.forClass(NewBlockMessage.class); + final ArgumentCaptor receivingPeerCaptor = + ArgumentCaptor.forClass(PeerConnection.class); + final ArgumentCaptor capabilityCaptor = ArgumentCaptor.forClass(Capability.class); - // assert that all entries in capability param were Eth63 - assertThat(capabilityCaptor.getAllValues().stream().distinct().collect(Collectors.toList())) - .isEqualTo(Collections.singletonList(EthProtocol.ETH63)); + verify(onSend, times(PEER_COUNT)) + .exec( + capabilityCaptor.capture(), + messageSentCaptor.capture(), + receivingPeerCaptor.capture()); - // assert that all messages transmitted contain the expected block & total difficulty. - final ProtocolSchedule protocolSchdeule = MainnetProtocolSchedule.create(); - for (final NewBlockMessage msg : messageSentCaptor.getAllValues()) { - assertThat(msg.block(protocolSchdeule)).isEqualTo(minedBlock); - assertThat(msg.totalDifficulty(protocolSchdeule)).isEqualTo(expectedTotalDifficulty); - } + // assert that all entries in capability param were Eth63 + assertThat(capabilityCaptor.getAllValues().stream().distinct().collect(Collectors.toList())) + .isEqualTo(Collections.singletonList(EthProtocol.ETH63)); - assertThat(receivingPeerCaptor.getAllValues().containsAll(peers)).isTrue(); + // assert that all messages transmitted contain the expected block & total difficulty. + final ProtocolSchedule protocolSchdeule = MainnetProtocolSchedule.create(); + for (final NewBlockMessage msg : messageSentCaptor.getAllValues()) { + assertThat(msg.block(protocolSchdeule)).isEqualTo(minedBlock); + assertThat(msg.totalDifficulty(protocolSchdeule)).isEqualTo(expectedTotalDifficulty); + } + + assertThat(receivingPeerCaptor.getAllValues().containsAll(peers)).isTrue(); + } } @Test @@ -1014,18 +905,13 @@ public final class EthProtocolManagerTest { final CompletableFuture done = new CompletableFuture<>(); try (final EthProtocolManager ethManager = - new EthProtocolManager( + EthProtocolManagerTestUtil.create( blockchain, + () -> false, protocolContext.getWorldStateArchive(), - BigInteger.ONE, - Collections.emptyList(), - true, - 1, - 1, - 1, - TestClock.fixed(), - new NoOpMetricsSystem(), + transactionPool, EthProtocolConfiguration.defaultConfig())) { + final long startBlock = 1L; final int requestedBlockCount = 13; final int receivedBlockCount = 2; @@ -1084,18 +970,12 @@ public final class EthProtocolManagerTest { final TransactionsMessage transactionMessage = TransactionsMessage.readFrom(raw); try (final EthProtocolManager ethManager = - new EthProtocolManager( + EthProtocolManagerTestUtil.create( blockchain, - protocolContext.getWorldStateArchive(), - BigInteger.ONE, - Collections.emptyList(), - true, ethScheduler, - EthProtocolConfiguration.defaultConfig(), - TestClock.fixed(), - metricsSystem, - new ForkIdManager(blockchain, Collections.emptyList()))) { - + protocolContext.getWorldStateArchive(), + transactionPool, + EthProtocolConfiguration.defaultConfig())) { // Create a transaction pool. This has a side effect of registering a listener for the // transactions message. TransactionPoolFactory.createTransactionPool( diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTestUtil.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTestUtil.java index 2ecba9f725..7109d3ebbf 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTestUtil.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTestUtil.java @@ -16,17 +16,19 @@ package org.hyperledger.besu.ethereum.eth.manager; import static com.google.common.base.Preconditions.checkArgument; import static org.hyperledger.besu.ethereum.core.InMemoryStorageProvider.createInMemoryBlockchain; -import static org.hyperledger.besu.ethereum.core.InMemoryStorageProvider.createInMemoryWorldStateArchive; +import static org.mockito.Mockito.mock; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.ChainHead; import org.hyperledger.besu.ethereum.chain.GenesisState; +import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.eth.EthProtocol; import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.manager.DeterministicEthScheduler.TimeoutPolicy; import org.hyperledger.besu.ethereum.eth.peervalidation.PeerValidator; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.DefaultMessage; @@ -43,32 +45,58 @@ public class EthProtocolManagerTestUtil { public static EthProtocolManager create( final Blockchain blockchain, + final TimeoutPolicy timeoutPolicy, final WorldStateArchive worldStateArchive, - final TimeoutPolicy timeoutPolicy) { - return create(blockchain, worldStateArchive, new DeterministicEthScheduler(timeoutPolicy)); + final TransactionPool transactionPool, + final EthProtocolConfiguration ethereumWireProtocolConfiguration) { + return create( + blockchain, + new DeterministicEthScheduler(timeoutPolicy), + worldStateArchive, + transactionPool, + ethereumWireProtocolConfiguration); } public static EthProtocolManager create( final Blockchain blockchain, + final EthScheduler ethScheduler, final WorldStateArchive worldStateArchive, - final EthScheduler ethScheduler) { + final TransactionPool transactionPool, + final EthProtocolConfiguration ethereumWireProtocolConfiguration, + final EthPeers ethPeers, + final EthMessages ethMessages, + final EthContext ethContext) { final BigInteger networkId = BigInteger.ONE; return new EthProtocolManager( blockchain, - worldStateArchive, networkId, + worldStateArchive, + transactionPool, + ethereumWireProtocolConfiguration, + ethPeers, + ethMessages, + ethContext, Collections.emptyList(), false, ethScheduler, - EthProtocolConfiguration.defaultConfig(), - TestClock.fixed(), - new NoOpMetricsSystem(), new ForkIdManager(blockchain, Collections.emptyList())); } + public static EthProtocolManager create(final Blockchain blockchain) { + return create(blockchain, new DeterministicEthScheduler(TimeoutPolicy.NEVER_TIMEOUT)); + } + public static EthProtocolManager create( - final Blockchain blockchain, final WorldStateArchive worldStateArchive) { - return create(blockchain, worldStateArchive, TimeoutPolicy.NEVER_TIMEOUT); + final Blockchain blockchain, + final WorldStateArchive worldStateArchive, + final TransactionPool transactionPool, + final EthProtocolConfiguration ethProtocolConfiguration) { + return create( + blockchain, + new DeterministicEthScheduler(TimeoutPolicy.NEVER_TIMEOUT), + worldStateArchive, + transactionPool, + ethProtocolConfiguration); } public static EthProtocolManager create(final EthScheduler ethScheduler) { @@ -76,8 +104,43 @@ public class EthProtocolManagerTestUtil { final GenesisConfigFile config = GenesisConfigFile.mainnet(); final GenesisState genesisState = GenesisState.fromConfig(config, protocolSchedule); final Blockchain blockchain = createInMemoryBlockchain(genesisState.getBlock()); - final WorldStateArchive worldStateArchive = createInMemoryWorldStateArchive(); - return create(blockchain, worldStateArchive, ethScheduler); + return create(blockchain, ethScheduler); + } + + public static EthProtocolManager create( + final Blockchain blockchain, + final EthScheduler ethScheduler, + final WorldStateArchive worldStateArchive, + final TransactionPool transactionPool, + final EthProtocolConfiguration configuration) { + EthPeers peers = new EthPeers(EthProtocol.NAME, TestClock.fixed(), new NoOpMetricsSystem()); + EthMessages messages = new EthMessages(); + + return create( + blockchain, + ethScheduler, + worldStateArchive, + transactionPool, + configuration, + peers, + messages, + new EthContext(peers, messages, ethScheduler)); + } + + public static EthProtocolManager create( + final Blockchain blockchain, final EthScheduler ethScheduler) { + EthPeers peers = new EthPeers(EthProtocol.NAME, TestClock.fixed(), new NoOpMetricsSystem()); + EthMessages messages = new EthMessages(); + + return create( + blockchain, + ethScheduler, + BlockchainSetupUtil.forTesting().getWorldArchive(), + mock(TransactionPool.class), + EthProtocolConfiguration.defaultConfig(), + peers, + messages, + new EthContext(peers, messages, ethScheduler)); } public static EthProtocolManager create() { diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthServerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthServerTest.java index 1c2217df7a..731b9a0289 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthServerTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthServerTest.java @@ -25,6 +25,7 @@ import org.hyperledger.besu.ethereum.core.Hash; import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.messages.GetNodeDataMessage; import org.hyperledger.besu.ethereum.eth.messages.NodeDataMessage; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import java.util.Optional; @@ -43,13 +44,18 @@ public class EthServerTest { private static final Hash HASH3 = Hash.hash(VALUE3); private final Blockchain blockchain = mock(Blockchain.class); private final WorldStateArchive worldStateArchive = mock(WorldStateArchive.class); + private final TransactionPool transactionPool = mock(TransactionPool.class); private final EthPeer ethPeer = mock(EthPeer.class); private final EthMessages ethMessages = new EthMessages(); @Before public void setUp() { new EthServer( - blockchain, worldStateArchive, ethMessages, new EthProtocolConfiguration(2, 2, 2, 2)); + blockchain, + worldStateArchive, + transactionPool, + ethMessages, + new EthProtocolConfiguration(2, 2, 2, 2, 2)); } @Test 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 2051f7de0e..4439c68416 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 @@ -17,6 +17,7 @@ package org.hyperledger.besu.ethereum.eth.manager; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static org.hyperledger.besu.ethereum.core.InMemoryStorageProvider.createInMemoryWorldStateArchive; +import static org.mockito.Mockito.mock; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockBody; @@ -24,15 +25,19 @@ import org.hyperledger.besu.ethereum.core.BlockDataGenerator; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.eth.EthProtocol; import org.hyperledger.besu.ethereum.eth.messages.BlockBodiesMessage; import org.hyperledger.besu.ethereum.eth.messages.BlockHeadersMessage; import org.hyperledger.besu.ethereum.eth.messages.EthPV62; import org.hyperledger.besu.ethereum.eth.messages.EthPV63; +import org.hyperledger.besu.ethereum.eth.messages.EthPV65; import org.hyperledger.besu.ethereum.eth.messages.NodeDataMessage; +import org.hyperledger.besu.ethereum.eth.messages.PooledTransactionsMessage; import org.hyperledger.besu.ethereum.eth.messages.ReceiptsMessage; import org.hyperledger.besu.ethereum.eth.peervalidation.PeerValidator; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Capability; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.DefaultMessage; @@ -223,12 +228,24 @@ public class RespondingEthPeer { }; } - public static Responder blockchainResponder(final Blockchain blockchain) { - return blockchainResponder(blockchain, createInMemoryWorldStateArchive()); + private static TransactionPool createTransactionPool() { + return mock(TransactionPool.class); } public static Responder blockchainResponder( final Blockchain blockchain, final WorldStateArchive worldStateArchive) { + return blockchainResponder(blockchain, worldStateArchive, createTransactionPool()); + } + + public static Responder blockchainResponder(final Blockchain blockchain) { + return blockchainResponder( + blockchain, createInMemoryWorldStateArchive(), createTransactionPool()); + } + + public static Responder blockchainResponder( + final Blockchain blockchain, + final WorldStateArchive worldStateArchive, + final TransactionPool transactionPool) { return (cap, msg) -> { MessageData response = null; switch (msg.getCode()) { @@ -244,6 +261,8 @@ public class RespondingEthPeer { case EthPV63.GET_NODE_DATA: response = EthServer.constructGetNodeDataResponse(worldStateArchive, msg, 200); break; + case EthPV65.GET_POOLED_TRANSACTIONS: + response = EthServer.constructGetPooledTransactionsResponse(transactionPool, msg, 200); } return Optional.ofNullable(response); }; @@ -265,11 +284,13 @@ public class RespondingEthPeer { public static Responder partialResponder( final Blockchain blockchain, final WorldStateArchive worldStateArchive, + final TransactionPool transactionPool, final ProtocolSchedule protocolSchedule, final float portion) { checkArgument(portion >= 0.0 && portion <= 1.0, "Portion is in the range [0.0..1.0]"); - final Responder fullResponder = blockchainResponder(blockchain, worldStateArchive); + final Responder fullResponder = + blockchainResponder(blockchain, worldStateArchive, transactionPool); return (cap, msg) -> { final Optional maybeResponse = fullResponder.respond(cap, msg); if (!maybeResponse.isPresent()) { @@ -310,6 +331,15 @@ public class RespondingEthPeer { originalNodeData.subList(0, (int) (originalNodeData.size() * portion)); partialResponse = NodeDataMessage.create(partialNodeData); break; + case EthPV65.GET_POOLED_TRANSACTIONS: + final PooledTransactionsMessage pooledTransactionsMessage = + PooledTransactionsMessage.readFrom(originalResponse); + final List originalPooledTx = + Lists.newArrayList(pooledTransactionsMessage.transactions()); + final List partialPooledTx = + originalPooledTx.subList(0, (int) (originalPooledTx.size() * portion)); + partialResponse = PooledTransactionsMessage.create(partialPooledTx); + break; } return Optional.of(partialResponse); }; @@ -331,6 +361,9 @@ public class RespondingEthPeer { case EthPV63.GET_NODE_DATA: response = NodeDataMessage.create(Collections.emptyList()); break; + case EthPV65.GET_POOLED_TRANSACTIONS: + response = PooledTransactionsMessage.create(Collections.emptyList()); + break; } return Optional.ofNullable(response); }; 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 4ada00c57f..902c54a471 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 @@ -19,15 +19,27 @@ import static org.assertj.core.api.Assertions.assertThat; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; +import org.hyperledger.besu.ethereum.core.Wei; +import org.hyperledger.besu.ethereum.eth.EthProtocol; +import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; +import org.hyperledger.besu.ethereum.eth.manager.DeterministicEthScheduler; import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.manager.EthMessages; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; +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.EthScheduler; import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer; import org.hyperledger.besu.ethereum.eth.manager.task.EthTask; +import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolFactory; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.testutil.TestClock; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; @@ -49,6 +61,7 @@ public abstract class AbstractMessageTaskTest { protected static MetricsSystem metricsSystem = new NoOpMetricsSystem(); protected EthProtocolManager ethProtocolManager; protected EthContext ethContext; + protected TransactionPool transactionPool; protected AtomicBoolean peersDoTimeout; protected AtomicInteger peerCountToTimeout; @@ -59,6 +72,7 @@ public abstract class AbstractMessageTaskTest { blockchain = blockchainSetupUtil.getBlockchain(); protocolSchedule = blockchainSetupUtil.getProtocolSchedule(); protocolContext = blockchainSetupUtil.getProtocolContext(); + assert (blockchainSetupUtil.getMaxBlockNumber() >= 20L); } @@ -66,12 +80,33 @@ public abstract class AbstractMessageTaskTest { public void setupTest() { peersDoTimeout = new AtomicBoolean(false); peerCountToTimeout = new AtomicInteger(0); + final EthPeers ethPeers = new EthPeers(EthProtocol.NAME, TestClock.fixed(), metricsSystem); + final EthMessages ethMessages = new EthMessages(); + final EthScheduler ethScheduler = + new DeterministicEthScheduler( + () -> peerCountToTimeout.getAndDecrement() > 0 || peersDoTimeout.get()); + ethContext = new EthContext(ethPeers, ethMessages, ethScheduler); + final SyncState syncState = new SyncState(blockchain, ethContext.getEthPeers()); + transactionPool = + TransactionPoolFactory.createTransactionPool( + protocolSchedule, + protocolContext, + ethContext, + TestClock.fixed(), + metricsSystem, + syncState, + Wei.of(1), + TransactionPoolConfiguration.builder().build()); ethProtocolManager = EthProtocolManagerTestUtil.create( blockchain, + ethScheduler, protocolContext.getWorldStateArchive(), - () -> peerCountToTimeout.getAndDecrement() > 0 || peersDoTimeout.get()); - ethContext = ethProtocolManager.ethContext(); + transactionPool, + EthProtocolConfiguration.defaultConfig(), + ethPeers, + ethMessages, + ethContext); } protected abstract T generateDataToBeRequested(); @@ -85,7 +120,8 @@ public abstract class AbstractMessageTaskTest { public void completesWhenPeersAreResponsive() { // Setup a responsive peer final RespondingEthPeer.Responder responder = - RespondingEthPeer.blockchainResponder(blockchain, protocolContext.getWorldStateArchive()); + RespondingEthPeer.blockchainResponder( + blockchain, protocolContext.getWorldStateArchive(), transactionPool); final RespondingEthPeer respondingPeer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/PeerMessageTaskTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/PeerMessageTaskTest.java index 1d4df7b483..a2eb657d43 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/PeerMessageTaskTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/PeerMessageTaskTest.java @@ -43,7 +43,11 @@ public abstract class PeerMessageTaskTest // Setup a partially responsive peer final RespondingEthPeer.Responder responder = RespondingEthPeer.partialResponder( - blockchain, protocolContext.getWorldStateArchive(), protocolSchedule, 0.5f); + blockchain, + protocolContext.getWorldStateArchive(), + transactionPool, + protocolSchedule, + 0.5f); final RespondingEthPeer respondingEthPeer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/RetryingMessageTaskTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/RetryingMessageTaskTest.java index 2ccea9266e..fde91d87d6 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/RetryingMessageTaskTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/RetryingMessageTaskTest.java @@ -54,7 +54,11 @@ public abstract class RetryingMessageTaskTest extends AbstractMessageTaskTest // Setup a partially responsive peer and a non-responsive peer final RespondingEthPeer.Responder partialResponder = RespondingEthPeer.partialResponder( - blockchain, protocolContext.getWorldStateArchive(), protocolSchedule, 0.5f); + blockchain, + protocolContext.getWorldStateArchive(), + transactionPool, + protocolSchedule, + 0.5f); final RespondingEthPeer.Responder emptyResponder = RespondingEthPeer.emptyResponder(); final RespondingEthPeer respondingPeer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager); @@ -100,16 +104,32 @@ public abstract class RetryingMessageTaskTest extends AbstractMessageTaskTest // Respond with partial data up until complete. respondingPeer.respond( RespondingEthPeer.partialResponder( - blockchain, protocolContext.getWorldStateArchive(), protocolSchedule, 0.25f)); + blockchain, + protocolContext.getWorldStateArchive(), + transactionPool, + protocolSchedule, + 0.25f)); respondingPeer.respond( RespondingEthPeer.partialResponder( - blockchain, protocolContext.getWorldStateArchive(), protocolSchedule, 0.50f)); + blockchain, + protocolContext.getWorldStateArchive(), + transactionPool, + protocolSchedule, + 0.50f)); respondingPeer.respond( RespondingEthPeer.partialResponder( - blockchain, protocolContext.getWorldStateArchive(), protocolSchedule, 0.75f)); + blockchain, + protocolContext.getWorldStateArchive(), + transactionPool, + protocolSchedule, + 0.75f)); respondingPeer.respond( RespondingEthPeer.partialResponder( - blockchain, protocolContext.getWorldStateArchive(), protocolSchedule, 1.0f)); + blockchain, + protocolContext.getWorldStateArchive(), + transactionPool, + protocolSchedule, + 1.0f)); assertThat(future.isDone()).isTrue(); assertResultMatchesExpectation(requestedData, future.get(), respondingPeer.getEthPeer()); @@ -140,7 +160,8 @@ public abstract class RetryingMessageTaskTest extends AbstractMessageTaskTest // Setup a peer final RespondingEthPeer.Responder responder = - RespondingEthPeer.blockchainResponder(blockchain, protocolContext.getWorldStateArchive()); + RespondingEthPeer.blockchainResponder( + blockchain, protocolContext.getWorldStateArchive(), transactionPool); final RespondingEthPeer respondingPeer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager); respondingPeer.respondWhile(responder, () -> !future.isDone()); @@ -153,7 +174,8 @@ public abstract class RetryingMessageTaskTest extends AbstractMessageTaskTest throws ExecutionException, InterruptedException { peerCountToTimeout.set(1); final RespondingEthPeer.Responder responder = - RespondingEthPeer.blockchainResponder(blockchain, protocolContext.getWorldStateArchive()); + RespondingEthPeer.blockchainResponder( + blockchain, protocolContext.getWorldStateArchive(), transactionPool); final RespondingEthPeer respondingPeer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager); final T requestedData = generateDataToBeRequested(); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/GetPooledTransactionsFromPeerTaskTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/GetPooledTransactionsFromPeerTaskTest.java new file mode 100644 index 0000000000..32586b40b4 --- /dev/null +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/GetPooledTransactionsFromPeerTaskTest.java @@ -0,0 +1,87 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.manager.task; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.crypto.SECP256K1; +import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.TransactionTestFixture; +import org.hyperledger.besu.ethereum.eth.manager.EthPeer; +import org.hyperledger.besu.ethereum.eth.manager.ethtaskutils.PeerMessageTaskTest; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.MetricsSystem; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import com.google.common.collect.Lists; + +public class GetPooledTransactionsFromPeerTaskTest extends PeerMessageTaskTest> { + + private final MetricsSystem metricsSystem = new NoOpMetricsSystem(); + + @Override + protected List generateDataToBeRequested() { + + final List requestedData = new ArrayList<>(); + SECP256K1.KeyPair keyPair = SECP256K1.KeyPair.generate(); + for (int i = 0; i < 3; i++) { + Transaction tx = + new TransactionTestFixture() + .nonce(i) + .gasLimit(100000) + .chainId(Optional.empty()) + .createTransaction(keyPair); + assertThat(transactionPool.getPendingTransactions().addLocalTransaction(tx)).isTrue(); + requestedData.add(tx); + } + return requestedData; + } + + @Override + protected EthTask>> createTask( + final List requestedData) { + final List hashes = + Lists.newArrayList(requestedData).stream() + .map(Transaction::getHash) + .collect(Collectors.toList()); + return GetPooledTransactionsFromPeerTask.forHashes(ethContext, hashes, metricsSystem); + } + + @Override + protected void assertPartialResultMatchesExpectation( + final List requestedData, final List partialResponse) { + assertThat(partialResponse.size()).isLessThanOrEqualTo(requestedData.size()); + assertThat(partialResponse.size()).isGreaterThan(0); + for (Transaction data : partialResponse) { + assertThat(requestedData).contains(data); + } + } + + @Override + protected void assertResultMatchesExpectation( + final List requestedData, + final AbstractPeerTask.PeerTaskResult> response, + final EthPeer respondingPeer) { + assertThat(response.getResult().size()).isEqualTo(requestedData.size()); + for (Transaction data : response.getResult()) { + assertThat(requestedData).contains(data); + } + } +} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/RetryingGetNodeDataFromPeerTaskTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/RetryingGetNodeDataFromPeerTaskTest.java index 8523d51dfb..c38468737a 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/RetryingGetNodeDataFromPeerTaskTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/RetryingGetNodeDataFromPeerTaskTest.java @@ -74,7 +74,11 @@ public class RetryingGetNodeDataFromPeerTaskTest extends RetryingMessageTaskTest // Respond with partial data. respondingPeer.respond( RespondingEthPeer.partialResponder( - blockchain, protocolContext.getWorldStateArchive(), protocolSchedule, 0.50f)); + blockchain, + protocolContext.getWorldStateArchive(), + transactionPool, + protocolSchedule, + 0.50f)); assertThat(future.isDone()).isTrue(); // Check that it immediately returns the data we got in the response. diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/GetPooledTransactionsMessageTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/GetPooledTransactionsMessageTest.java new file mode 100644 index 0000000000..b0610304d2 --- /dev/null +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/GetPooledTransactionsMessageTest.java @@ -0,0 +1,47 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.messages; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.ethereum.p2p.rlpx.wire.RawMessage; + +import java.util.Arrays; +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.junit.Test; + +public class GetPooledTransactionsMessageTest { + + @Test + public void roundTripGetPooledTransactionsMessage() { + List hashes = Arrays.asList(Hash.wrap(Bytes32.random())); + final GetPooledTransactionsMessage msg = GetPooledTransactionsMessage.create(hashes); + assertThat(msg.getCode()).isEqualTo(EthPV65.GET_POOLED_TRANSACTIONS); + assertThat(msg.pooledTransactions()).isEqualTo(hashes); + } + + @Test + public void readFromMessageWithWrongCodeThrows() { + final RawMessage rawMsg = new RawMessage(EthPV62.BLOCK_HEADERS, Bytes.of(0)); + + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> GetPooledTransactionsMessage.readFrom(rawMsg)); + } +} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/LimitedNewPooledTransactionHashesMessagesTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/LimitedNewPooledTransactionHashesMessagesTest.java new file mode 100644 index 0000000000..b5fa852c6e --- /dev/null +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/LimitedNewPooledTransactionHashesMessagesTest.java @@ -0,0 +1,71 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.messages; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.ethereum.core.BlockDataGenerator; +import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.ethereum.core.Transaction; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.Test; + +public class LimitedNewPooledTransactionHashesMessagesTest { + + private final BlockDataGenerator generator = new BlockDataGenerator(); + private final List sampleTxs = + generator.transactions(1).stream().map(Transaction::getHash).collect(Collectors.toList()); + private final NewPooledTransactionHashesMessage sampleTransactionMessages = + NewPooledTransactionHashesMessage.create(sampleTxs); + private final LimitedNewPooledTransactionHashesMessages sampleLimitedTransactionsMessages = + new LimitedNewPooledTransactionHashesMessages(sampleTransactionMessages, sampleTxs); + + @Test + public void createLimited() { + final List txs = + generator.transactions(6000).stream() + .map(Transaction::getHash) + .collect(Collectors.toList()); + final LimitedNewPooledTransactionHashesMessages firstMessage = + LimitedNewPooledTransactionHashesMessages.createLimited(txs); + assertThat(firstMessage.getIncludedTransactions().size()).isEqualTo(4096); + + txs.removeAll(firstMessage.getIncludedTransactions()); + assertThat(txs.size()).isEqualTo(6000 - 4096); + final LimitedNewPooledTransactionHashesMessages secondMessage = + LimitedNewPooledTransactionHashesMessages.createLimited(txs); + assertThat(secondMessage.getIncludedTransactions().size()).isEqualTo(6000 - 4096); + txs.removeAll(secondMessage.getIncludedTransactions()); + assertThat(txs.size()).isEqualTo(0); + assertThat( + firstMessage.getTransactionsMessage().getSize() + + secondMessage.getTransactionsMessage().getSize()) + .isLessThan(2 * LimitedTransactionsMessages.LIMIT); + } + + @Test + public void getTransactionsMessage() { + assertThat(sampleLimitedTransactionsMessages.getTransactionsMessage()) + .isEqualTo(sampleTransactionMessages); + } + + @Test + public void getIncludedTransactions() { + assertThat(sampleLimitedTransactionsMessages.getIncludedTransactions()).isEqualTo(sampleTxs); + } +} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/NewPooledTransactionHashesMessageTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/NewPooledTransactionHashesMessageTest.java new file mode 100644 index 0000000000..88a9ec3aeb --- /dev/null +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/NewPooledTransactionHashesMessageTest.java @@ -0,0 +1,47 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.messages; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.ethereum.p2p.rlpx.wire.RawMessage; + +import java.util.Arrays; +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.junit.Test; + +public class NewPooledTransactionHashesMessageTest { + + @Test + public void roundTripNewPooledTransactionHashesMessage() { + List hashes = Arrays.asList(Hash.wrap(Bytes32.random())); + final NewPooledTransactionHashesMessage msg = NewPooledTransactionHashesMessage.create(hashes); + assertThat(msg.getCode()).isEqualTo(EthPV65.NEW_POOLED_TRANSACTION_HASHES); + assertThat(msg.pendingTransactions()).isEqualTo(hashes); + } + + @Test + public void readFromMessageWithWrongCodeThrows() { + final RawMessage rawMsg = new RawMessage(EthPV62.BLOCK_HEADERS, Bytes.of(0)); + + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> NewPooledTransactionHashesMessage.readFrom(rawMsg)); + } +} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/PooledTransactionsMessageTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/PooledTransactionsMessageTest.java new file mode 100644 index 0000000000..20207c7026 --- /dev/null +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/PooledTransactionsMessageTest.java @@ -0,0 +1,56 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.messages; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import org.hyperledger.besu.crypto.SECP256K1; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.Wei; +import org.hyperledger.besu.ethereum.p2p.rlpx.wire.RawMessage; + +import java.util.Arrays; +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.Test; + +public class PooledTransactionsMessageTest { + + @Test + public void roundTripPooledTransactionsMessage() { + List tx = + Arrays.asList( + Transaction.builder() + .nonce(42) + .gasLimit(654321) + .gasPrice(Wei.of(2)) + .value(Wei.of(1337)) + .payload(Bytes.EMPTY) + .signAndBuild(SECP256K1.KeyPair.generate())); + final PooledTransactionsMessage msg = PooledTransactionsMessage.create(tx); + assertThat(msg.getCode()).isEqualTo(EthPV65.POOLED_TRANSACTIONS); + assertThat(msg.transactions()).isEqualTo(tx); + } + + @Test + public void readFromMessageWithWrongCodeThrows() { + final RawMessage rawMsg = new RawMessage(EthPV62.BLOCK_HEADERS, Bytes.of(0)); + + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> PooledTransactionsMessage.readFrom(rawMsg)); + } +} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/BlockPropagationManagerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/BlockPropagationManagerTest.java index 4077f504c6..9e5b29c113 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/BlockPropagationManagerTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/BlockPropagationManagerTest.java @@ -34,6 +34,7 @@ import org.hyperledger.besu.ethereum.core.BlockDataGenerator.BlockOptions; import org.hyperledger.besu.ethereum.core.BlockImporter; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthMessages; import org.hyperledger.besu.ethereum.eth.manager.EthPeers; @@ -93,7 +94,11 @@ public class BlockPropagationManagerTest { tempProtocolContext.getWorldStateArchive(), tempProtocolContext.getConsensusState()); ethProtocolManager = - EthProtocolManagerTestUtil.create(blockchain, blockchainUtil.getWorldArchive()); + EthProtocolManagerTestUtil.create( + blockchain, + blockchainUtil.getWorldArchive(), + blockchainUtil.getTransactionPool(), + EthProtocolConfiguration.defaultConfig()); syncConfig = SynchronizerConfiguration.builder().blockPropagationRange(-3, 5).build(); syncState = new SyncState(blockchain, ethProtocolManager.ethContext().getEthPeers()); blockBroadcaster = mock(BlockBroadcaster.class); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/ChainHeadTrackerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/ChainHeadTrackerTest.java index 806ffc6576..6b8e508e04 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/ChainHeadTrackerTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/ChainHeadTrackerTest.java @@ -38,7 +38,7 @@ public class ChainHeadTrackerTest { private final BlockchainSetupUtil blockchainSetupUtil = BlockchainSetupUtil.forTesting(); private final MutableBlockchain blockchain = blockchainSetupUtil.getBlockchain(); private final EthProtocolManager ethProtocolManager = - EthProtocolManagerTestUtil.create(blockchain, blockchainSetupUtil.getWorldArchive()); + EthProtocolManagerTestUtil.create(blockchain); private final RespondingEthPeer respondingPeer = RespondingEthPeer.builder() .ethProtocolManager(ethProtocolManager) @@ -62,7 +62,9 @@ public class ChainHeadTrackerTest { public void shouldRequestHeaderChainHeadWhenNewPeerConnects() { final Responder responder = RespondingEthPeer.blockchainResponder( - blockchainSetupUtil.getBlockchain(), blockchainSetupUtil.getWorldArchive()); + blockchainSetupUtil.getBlockchain(), + blockchainSetupUtil.getWorldArchive(), + blockchainSetupUtil.getTransactionPool()); chainHeadTracker.onPeerConnected(respondingPeer.getEthPeer()); Assertions.assertThat(chainHeadState().getEstimatedHeight()).isZero(); @@ -77,7 +79,9 @@ public class ChainHeadTrackerTest { public void shouldIgnoreHeadersIfChainHeadHasAlreadyBeenUpdatedWhileWaiting() { final Responder responder = RespondingEthPeer.blockchainResponder( - blockchainSetupUtil.getBlockchain(), blockchainSetupUtil.getWorldArchive()); + blockchainSetupUtil.getBlockchain(), + blockchainSetupUtil.getWorldArchive(), + blockchainSetupUtil.getTransactionPool()); chainHeadTracker.onPeerConnected(respondingPeer.getEthPeer()); // Change the hash of the current known head @@ -92,7 +96,9 @@ public class ChainHeadTrackerTest { public void shouldCheckTrialingPeerLimits() { final Responder responder = RespondingEthPeer.blockchainResponder( - blockchainSetupUtil.getBlockchain(), blockchainSetupUtil.getWorldArchive()); + blockchainSetupUtil.getBlockchain(), + blockchainSetupUtil.getWorldArchive(), + blockchainSetupUtil.getTransactionPool()); chainHeadTracker.onPeerConnected(respondingPeer.getEthPeer()); Assertions.assertThat(chainHeadState().getEstimatedHeight()).isZero(); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/CheckpointHeaderFetcherTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/CheckpointHeaderFetcherTest.java index 7b0ff391fc..965f4bdfda 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/CheckpointHeaderFetcherTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/CheckpointHeaderFetcherTest.java @@ -23,11 +23,13 @@ import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; +import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManagerTestUtil; import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer; import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer.Responder; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; @@ -50,6 +52,7 @@ public class CheckpointHeaderFetcherTest { private static ProtocolSchedule protocolSchedule; private static ProtocolContext protocolContext; private static final MetricsSystem metricsSystem = new NoOpMetricsSystem(); + private static TransactionPool transactionPool; private EthProtocolManager ethProtocolManager; private Responder responder; private RespondingEthPeer respondingPeer; @@ -59,6 +62,7 @@ public class CheckpointHeaderFetcherTest { final BlockchainSetupUtil blockchainSetupUtil = BlockchainSetupUtil.forTesting(); blockchainSetupUtil.importAllBlocks(); blockchain = blockchainSetupUtil.getBlockchain(); + transactionPool = blockchainSetupUtil.getTransactionPool(); protocolSchedule = blockchainSetupUtil.getProtocolSchedule(); protocolContext = blockchainSetupUtil.getProtocolContext(); } @@ -67,9 +71,14 @@ public class CheckpointHeaderFetcherTest { public void setUpTest() { ethProtocolManager = EthProtocolManagerTestUtil.create( - blockchain, protocolContext.getWorldStateArchive(), () -> false); + blockchain, + () -> false, + protocolContext.getWorldStateArchive(), + transactionPool, + EthProtocolConfiguration.defaultConfig()); responder = - RespondingEthPeer.blockchainResponder(blockchain, protocolContext.getWorldStateArchive()); + RespondingEthPeer.blockchainResponder( + blockchain, protocolContext.getWorldStateArchive(), transactionPool); respondingPeer = EthProtocolManagerTestUtil.createPeer( ethProtocolManager, blockchain.getChainHeadBlockNumber()); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/DownloadHeadersStepTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/DownloadHeadersStepTest.java index 511aa353c3..59faaff46a 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/DownloadHeadersStepTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/DownloadHeadersStepTest.java @@ -62,8 +62,7 @@ public class DownloadHeadersStepTest { @Before public void setUp() { - ethProtocolManager = - EthProtocolManagerTestUtil.create(blockchain, protocolContext.getWorldStateArchive()); + ethProtocolManager = EthProtocolManagerTestUtil.create(blockchain); downloader = new DownloadHeadersStep<>( protocolSchedule, diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/DownloadReceiptsStepTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/DownloadReceiptsStepTest.java index 8740413e31..24e9bdfdf2 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/DownloadReceiptsStepTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/DownloadReceiptsStepTest.java @@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.eth.sync.fastsync; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; @@ -24,9 +25,11 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockWithReceipts; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; import org.hyperledger.besu.ethereum.core.TransactionReceipt; +import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManagerTestUtil; import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import java.util.List; @@ -54,8 +57,14 @@ public class DownloadReceiptsStepTest { @Before public void setUp() { + TransactionPool transactionPool = mock(TransactionPool.class); ethProtocolManager = - EthProtocolManagerTestUtil.create(blockchain, protocolContext.getWorldStateArchive()); + EthProtocolManagerTestUtil.create( + blockchain, + () -> false, + protocolContext.getWorldStateArchive(), + transactionPool, + EthProtocolConfiguration.defaultConfig()); downloadReceiptsStep = new DownloadReceiptsStep(ethProtocolManager.ethContext(), new NoOpMetricsSystem()); } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncActionsTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncActionsTest.java index 0513fe4a75..313ac46f3e 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncActionsTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncActionsTest.java @@ -26,6 +26,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; @@ -67,8 +68,10 @@ public class FastSyncActionsTest { ethProtocolManager = EthProtocolManagerTestUtil.create( blockchain, + () -> timeoutCount.getAndDecrement() > 0, blockchainSetupUtil.getWorldArchive(), - () -> timeoutCount.getAndDecrement() > 0); + blockchainSetupUtil.getTransactionPool(), + EthProtocolConfiguration.defaultConfig()); fastSyncActions = createFastSyncActions(syncConfig); } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncChainDownloaderTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncChainDownloaderTest.java index 66d33ebf95..d3b7a741dc 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncChainDownloaderTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncChainDownloaderTest.java @@ -70,9 +70,8 @@ public class FastSyncChainDownloaderTest { protocolContext = localBlockchainSetup.getProtocolContext(); ethProtocolManager = EthProtocolManagerTestUtil.create( - localBlockchain, - localBlockchainSetup.getWorldArchive(), - new EthScheduler(1, 1, 1, new NoOpMetricsSystem())); + localBlockchain, new EthScheduler(1, 1, 1, 1, new NoOpMetricsSystem())); + ethContext = ethProtocolManager.ethContext(); syncState = new SyncState(protocolContext.getBlockchain(), ethContext.getEthPeers()); } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockConfirmerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockConfirmerTest.java index a25c506675..b960d51353 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockConfirmerTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockConfirmerTest.java @@ -24,11 +24,13 @@ import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; +import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManagerTestUtil; import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer; import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer.Responder; import org.hyperledger.besu.ethereum.eth.sync.fastsync.PivotBlockConfirmer.ContestedPivotBlockException; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; @@ -51,6 +53,7 @@ public class PivotBlockConfirmerTest { private final AtomicBoolean timeout = new AtomicBoolean(false); private EthProtocolManager ethProtocolManager; private MutableBlockchain blockchain; + private TransactionPool transactionPool; private PivotBlockConfirmer pivotBlockConfirmer; private ProtocolSchedule protocolSchedule; @@ -59,11 +62,16 @@ public class PivotBlockConfirmerTest { final BlockchainSetupUtil blockchainSetupUtil = BlockchainSetupUtil.forTesting(); blockchainSetupUtil.importAllBlocks(); blockchain = blockchainSetupUtil.getBlockchain(); + transactionPool = blockchainSetupUtil.getTransactionPool(); protocolSchedule = blockchainSetupUtil.getProtocolSchedule(); protocolContext = blockchainSetupUtil.getProtocolContext(); ethProtocolManager = EthProtocolManagerTestUtil.create( - blockchain, blockchainSetupUtil.getWorldArchive(), timeout::get); + blockchain, + timeout::get, + blockchainSetupUtil.getWorldArchive(), + transactionPool, + EthProtocolConfiguration.defaultConfig()); pivotBlockConfirmer = createPivotBlockConfirmer(3, 1); } @@ -85,7 +93,8 @@ public class PivotBlockConfirmerTest { pivotBlockConfirmer = createPivotBlockConfirmer(2, 1); final Responder responder = - RespondingEthPeer.blockchainResponder(blockchain, protocolContext.getWorldStateArchive()); + RespondingEthPeer.blockchainResponder( + blockchain, protocolContext.getWorldStateArchive(), transactionPool); final RespondingEthPeer respondingPeerA = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); @@ -113,7 +122,8 @@ public class PivotBlockConfirmerTest { pivotBlockConfirmer = createPivotBlockConfirmer(2, 1); final Responder responder = - RespondingEthPeer.blockchainResponder(blockchain, protocolContext.getWorldStateArchive()); + RespondingEthPeer.blockchainResponder( + blockchain, protocolContext.getWorldStateArchive(), transactionPool); final RespondingEthPeer respondingPeerA = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); @@ -145,7 +155,8 @@ public class PivotBlockConfirmerTest { pivotBlockConfirmer = createPivotBlockConfirmer(2, 1); final Responder responder = - RespondingEthPeer.blockchainResponder(blockchain, protocolContext.getWorldStateArchive()); + RespondingEthPeer.blockchainResponder( + blockchain, protocolContext.getWorldStateArchive(), transactionPool); final RespondingEthPeer respondingPeerA = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); @@ -184,7 +195,8 @@ public class PivotBlockConfirmerTest { pivotBlockConfirmer = createPivotBlockConfirmer(2, 1); final Responder responder = - RespondingEthPeer.blockchainResponder(blockchain, protocolContext.getWorldStateArchive()); + RespondingEthPeer.blockchainResponder( + blockchain, protocolContext.getWorldStateArchive(), transactionPool); final RespondingEthPeer respondingPeerA = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); @@ -223,7 +235,8 @@ public class PivotBlockConfirmerTest { pivotBlockConfirmer = createPivotBlockConfirmer(2, 1); final Responder responder = - RespondingEthPeer.blockchainResponder(blockchain, protocolContext.getWorldStateArchive()); + RespondingEthPeer.blockchainResponder( + blockchain, protocolContext.getWorldStateArchive(), transactionPool); final RespondingEthPeer respondingPeerA = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); @@ -264,7 +277,8 @@ public class PivotBlockConfirmerTest { pivotBlockConfirmer = createPivotBlockConfirmer(3, 1); final Responder responderA = - RespondingEthPeer.blockchainResponder(blockchain, protocolContext.getWorldStateArchive()); + RespondingEthPeer.blockchainResponder( + blockchain, protocolContext.getWorldStateArchive(), transactionPool); final RespondingEthPeer respondingPeerA = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockRetrieverTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockRetrieverTest.java index 0646df63ed..50f8ca6a11 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockRetrieverTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockRetrieverTest.java @@ -26,11 +26,13 @@ import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManagerTestUtil; import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer; import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer.Responder; import org.hyperledger.besu.ethereum.eth.peervalidation.PeerValidator; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; @@ -54,6 +56,7 @@ public class PivotBlockRetrieverTest { private final AtomicBoolean timeout = new AtomicBoolean(false); private EthProtocolManager ethProtocolManager; private MutableBlockchain blockchain; + private TransactionPool transactionPool; private PivotBlockRetriever pivotBlockRetriever; private ProtocolSchedule protocolSchedule; @@ -64,9 +67,15 @@ public class PivotBlockRetrieverTest { blockchain = blockchainSetupUtil.getBlockchain(); protocolSchedule = blockchainSetupUtil.getProtocolSchedule(); protocolContext = blockchainSetupUtil.getProtocolContext(); + transactionPool = blockchainSetupUtil.getTransactionPool(); ethProtocolManager = EthProtocolManagerTestUtil.create( - blockchain, blockchainSetupUtil.getWorldArchive(), timeout::get); + blockchain, + timeout::get, + blockchainSetupUtil.getWorldArchive(), + transactionPool, + EthProtocolConfiguration.defaultConfig()); + pivotBlockRetriever = createPivotBlockRetriever(3, 1, 1); } @@ -87,7 +96,8 @@ public class PivotBlockRetrieverTest { @Test public void shouldSucceedWhenAllPeersAgree() { final RespondingEthPeer.Responder responder = - RespondingEthPeer.blockchainResponder(blockchain, protocolContext.getWorldStateArchive()); + RespondingEthPeer.blockchainResponder( + blockchain, protocolContext.getWorldStateArchive(), transactionPool); final RespondingEthPeer respondingPeerA = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); final RespondingEthPeer respondingPeerB = @@ -111,7 +121,8 @@ public class PivotBlockRetrieverTest { EthProtocolManagerTestUtil.disableEthSchedulerAutoRun(ethProtocolManager); final Responder responder = - RespondingEthPeer.blockchainResponder(blockchain, protocolContext.getWorldStateArchive()); + RespondingEthPeer.blockchainResponder( + blockchain, protocolContext.getWorldStateArchive(), transactionPool); final RespondingEthPeer respondingPeerA = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); final RespondingEthPeer badPeerA = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1); @@ -153,7 +164,8 @@ public class PivotBlockRetrieverTest { public void shouldIgnorePeersThatAreNotFullyValidated() { final PeerValidator peerValidator = mock(PeerValidator.class); final RespondingEthPeer.Responder responder = - RespondingEthPeer.blockchainResponder(blockchain, protocolContext.getWorldStateArchive()); + RespondingEthPeer.blockchainResponder( + blockchain, protocolContext.getWorldStateArchive(), transactionPool); final RespondingEthPeer respondingPeerA = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000, peerValidator); final RespondingEthPeer badPeerA = @@ -212,7 +224,8 @@ public class PivotBlockRetrieverTest { EthProtocolManagerTestUtil.disableEthSchedulerAutoRun(ethProtocolManager); final Responder responder = - RespondingEthPeer.blockchainResponder(blockchain, protocolContext.getWorldStateArchive()); + RespondingEthPeer.blockchainResponder( + blockchain, protocolContext.getWorldStateArchive(), transactionPool); final RespondingEthPeer peerA = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, Difficulty.of(1000), 1000); @@ -239,7 +252,8 @@ public class PivotBlockRetrieverTest { EthProtocolManagerTestUtil.disableEthSchedulerAutoRun(ethProtocolManager); final Responder responder = - RespondingEthPeer.blockchainResponder(blockchain, protocolContext.getWorldStateArchive()); + RespondingEthPeer.blockchainResponder( + blockchain, protocolContext.getWorldStateArchive(), transactionPool); final Responder emptyResponder = RespondingEthPeer.emptyResponder(); final RespondingEthPeer peerA = @@ -273,7 +287,8 @@ public class PivotBlockRetrieverTest { pivotBlockRetriever = createPivotBlockRetriever(2, pivotBlockDelta, 1); final Responder responderA = - RespondingEthPeer.blockchainResponder(blockchain, protocolContext.getWorldStateArchive()); + RespondingEthPeer.blockchainResponder( + blockchain, protocolContext.getWorldStateArchive(), transactionPool); final RespondingEthPeer respondingPeerA = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); @@ -306,7 +321,8 @@ public class PivotBlockRetrieverTest { pivotBlockRetriever = createPivotBlockRetriever(2, pivotBlockDelta, 1); final Responder responderA = - RespondingEthPeer.blockchainResponder(blockchain, protocolContext.getWorldStateArchive()); + RespondingEthPeer.blockchainResponder( + blockchain, protocolContext.getWorldStateArchive(), transactionPool); final RespondingEthPeer respondingPeerA = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); @@ -342,7 +358,8 @@ public class PivotBlockRetrieverTest { pivotBlockRetriever = createPivotBlockRetriever(2, pivotBlockDelta, 1); final Responder responderA = - RespondingEthPeer.blockchainResponder(blockchain, protocolContext.getWorldStateArchive()); + RespondingEthPeer.blockchainResponder( + blockchain, protocolContext.getWorldStateArchive(), transactionPool); final RespondingEthPeer respondingPeerA = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncChainDownloaderForkTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncChainDownloaderForkTest.java index 5dbdb962ce..2d67a83866 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncChainDownloaderForkTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncChainDownloaderForkTest.java @@ -21,6 +21,7 @@ import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManagerTestUtil; @@ -64,8 +65,10 @@ public class FullSyncChainDownloaderForkTest { ethProtocolManager = EthProtocolManagerTestUtil.create( localBlockchain, + new EthScheduler(1, 1, 1, 1, new NoOpMetricsSystem()), localBlockchainSetup.getWorldArchive(), - new EthScheduler(1, 1, 1, new NoOpMetricsSystem())); + localBlockchainSetup.getTransactionPool(), + EthProtocolConfiguration.defaultConfig()); ethContext = ethProtocolManager.ethContext(); syncState = new SyncState(protocolContext.getBlockchain(), ethContext.getEthPeers()); } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncChainDownloaderTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncChainDownloaderTest.java index 57f8d2d9a6..d3d4a74e5f 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncChainDownloaderTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncChainDownloaderTest.java @@ -28,6 +28,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.TransactionReceipt; +import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManagerTestUtil; @@ -83,8 +84,10 @@ public class FullSyncChainDownloaderTest { ethProtocolManager = EthProtocolManagerTestUtil.create( localBlockchain, + new EthScheduler(1, 1, 1, 1, new NoOpMetricsSystem()), localBlockchainSetup.getWorldArchive(), - new EthScheduler(1, 1, 1, new NoOpMetricsSystem())); + localBlockchainSetup.getTransactionPool(), + EthProtocolConfiguration.defaultConfig()); ethContext = ethProtocolManager.ethContext(); syncState = new SyncState(protocolContext.getBlockchain(), ethContext.getEthPeers()); } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncDownloaderTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncDownloaderTest.java index 4e94dc1384..46e357c0ce 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncDownloaderTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncDownloaderTest.java @@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.assertThat; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; +import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManagerTestUtil; @@ -57,8 +58,10 @@ public class FullSyncDownloaderTest { ethProtocolManager = EthProtocolManagerTestUtil.create( localBlockchain, + new EthScheduler(1, 1, 1, 1, new NoOpMetricsSystem()), localBlockchainSetup.getWorldArchive(), - new EthScheduler(1, 1, 1, new NoOpMetricsSystem())); + localBlockchainSetup.getTransactionPool(), + EthProtocolConfiguration.defaultConfig()); ethContext = ethProtocolManager.ethContext(); syncState = new SyncState(protocolContext.getBlockchain(), ethContext.getEthPeers()); } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncTargetManagerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncTargetManagerTest.java index 0e0eeb79bf..f0316ca897 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncTargetManagerTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncTargetManagerTest.java @@ -23,6 +23,7 @@ import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManagerTestUtil; @@ -65,7 +66,11 @@ public class FullSyncTargetManagerTest { new ProtocolContext<>(localBlockchain, localWorldState, null); ethProtocolManager = EthProtocolManagerTestUtil.create( - localBlockchain, localWorldState, new EthScheduler(1, 1, 1, new NoOpMetricsSystem())); + localBlockchain, + new EthScheduler(1, 1, 1, 1, new NoOpMetricsSystem()), + localWorldState, + localBlockchainSetup.getTransactionPool(), + EthProtocolConfiguration.defaultConfig()); final EthContext ethContext = ethProtocolManager.ethContext(); localBlockchainSetup.importFirstBlocks(5); otherBlockchainSetup.importFirstBlocks(20); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/state/SyncStateTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/state/SyncStateTest.java index e067ad85e6..a2eef01ad8 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/state/SyncStateTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/state/SyncStateTest.java @@ -90,9 +90,7 @@ public class SyncStateTest { @Before public void setUp() { - ethProtocolManager = - EthProtocolManagerTestUtil.create( - blockchain, InMemoryStorageProvider.createInMemoryWorldStateArchive()); + ethProtocolManager = EthProtocolManagerTestUtil.create(blockchain); ethPeers = spy(ethProtocolManager.ethContext().getEthPeers()); syncTargetPeer = createPeer(TARGET_DIFFICULTY, TARGET_CHAIN_HEIGHT); otherPeer = createPeer(Difficulty.ZERO, 0); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DetermineCommonAncestorTaskParameterizedTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DetermineCommonAncestorTaskParameterizedTest.java index f116f86f6a..75f7f6958d 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DetermineCommonAncestorTaskParameterizedTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DetermineCommonAncestorTaskParameterizedTest.java @@ -17,6 +17,7 @@ package org.hyperledger.besu.ethereum.eth.sync.tasks; import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.ethereum.core.InMemoryStorageProvider.createInMemoryBlockchain; import static org.hyperledger.besu.ethereum.core.InMemoryStorageProvider.createInMemoryWorldStateArchive; +import static org.mockito.Mockito.mock; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; @@ -26,11 +27,13 @@ import org.hyperledger.besu.ethereum.core.BlockDataGenerator; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.TransactionReceipt; +import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManagerTestUtil; import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer; import org.hyperledger.besu.ethereum.eth.manager.task.EthTask; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; import org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; @@ -140,8 +143,11 @@ public class DetermineCommonAncestorTaskParameterizedTest { final WorldStateArchive worldStateArchive = createInMemoryWorldStateArchive(); final EthProtocolManager ethProtocolManager = - EthProtocolManagerTestUtil.create(localBlockchain, worldStateArchive); - + EthProtocolManagerTestUtil.create( + localBlockchain, + worldStateArchive, + mock(TransactionPool.class), + EthProtocolConfiguration.defaultConfig()); final RespondingEthPeer.Responder responder = RespondingEthPeer.blockchainResponder(remoteBlockchain); final RespondingEthPeer respondingEthPeer = 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 b70a775f2a..7d5740b447 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 @@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -33,6 +34,7 @@ import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockDataGenerator; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.TransactionReceipt; +import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; @@ -40,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.task.EthTask; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; import org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; @@ -74,7 +77,12 @@ public class DetermineCommonAncestorTaskTest { localGenesisBlock = blockDataGenerator.genesisBlock(); localBlockchain = createInMemoryBlockchain(localGenesisBlock); final WorldStateArchive worldStateArchive = createInMemoryWorldStateArchive(); - ethProtocolManager = EthProtocolManagerTestUtil.create(localBlockchain, worldStateArchive); + ethProtocolManager = + EthProtocolManagerTestUtil.create( + localBlockchain, + worldStateArchive, + mock(TransactionPool.class), + EthProtocolConfiguration.defaultConfig()); ethContext = ethProtocolManager.ethContext(); protocolContext = new ProtocolContext<>(localBlockchain, worldStateArchive, null); } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldStateDownloaderTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldStateDownloaderTest.java index 0a6a57cbf1..14b78f2b68 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldStateDownloaderTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldStateDownloaderTest.java @@ -45,6 +45,7 @@ import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer; import org.hyperledger.besu.ethereum.eth.messages.EthPV63; import org.hyperledger.besu.ethereum.eth.messages.GetNodeDataMessage; import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSchedule; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; import org.hyperledger.besu.ethereum.rlp.RLP; @@ -111,7 +112,7 @@ public class WorldStateDownloaderTest { .build()); final EthProtocolManager ethProtocolManager = - EthProtocolManagerTestUtil.create(new EthScheduler(1, 1, 1, new NoOpMetricsSystem())); + EthProtocolManagerTestUtil.create(new EthScheduler(1, 1, 1, 1, new NoOpMetricsSystem())); @After public void tearDown() throws Exception { @@ -576,7 +577,8 @@ public class WorldStateDownloaderTest { // Respond to node data requests final List sentMessages = new ArrayList<>(); final RespondingEthPeer.Responder blockChainResponder = - RespondingEthPeer.blockchainResponder(mock(Blockchain.class), remoteWorldStateArchive); + RespondingEthPeer.blockchainResponder( + mock(Blockchain.class), remoteWorldStateArchive, mock(TransactionPool.class)); final RespondingEthPeer.Responder responder = RespondingEthPeer.wrapResponderWithCollector(blockChainResponder, sentMessages); @@ -606,7 +608,7 @@ public class WorldStateDownloaderTest { @Test public void stalledDownloader() { final EthProtocolManager ethProtocolManager = - EthProtocolManagerTestUtil.create(new EthScheduler(1, 1, 1, new NoOpMetricsSystem())); + EthProtocolManagerTestUtil.create(new EthScheduler(1, 1, 1, 1, new NoOpMetricsSystem())); // Setup "remote" state final WorldStateStorage remoteStorage = @@ -898,10 +900,15 @@ public class WorldStateDownloaderTest { final WorldStateArchive remoteWorldStateArchive, final CompletableFuture downloaderFuture) { final RespondingEthPeer.Responder fullResponder = - RespondingEthPeer.blockchainResponder(mock(Blockchain.class), remoteWorldStateArchive); + RespondingEthPeer.blockchainResponder( + mock(Blockchain.class), remoteWorldStateArchive, mock(TransactionPool.class)); final RespondingEthPeer.Responder partialResponder = RespondingEthPeer.partialResponder( - mock(Blockchain.class), remoteWorldStateArchive, MainnetProtocolSchedule.create(), .5f); + mock(Blockchain.class), + remoteWorldStateArchive, + mock(TransactionPool.class), + MainnetProtocolSchedule.create(), + .5f); final RespondingEthPeer.Responder emptyResponder = RespondingEthPeer.emptyResponder(); // Send a few partial responses diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PeerPendingTransactionTrackerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PeerPendingTransactionTrackerTest.java new file mode 100644 index 0000000000..1d016ffb2e --- /dev/null +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PeerPendingTransactionTrackerTest.java @@ -0,0 +1,106 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.transactions; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.ethereum.core.BlockDataGenerator; +import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.eth.manager.EthPeer; + +import java.util.Optional; + +import com.google.common.collect.ImmutableSet; +import org.junit.Before; +import org.junit.Test; + +public class PeerPendingTransactionTrackerTest { + + private final EthPeer ethPeer1 = mock(EthPeer.class); + private final EthPeer ethPeer2 = mock(EthPeer.class); + private final BlockDataGenerator generator = new BlockDataGenerator(); + private final PendingTransactions pendingTransactions = mock(PendingTransactions.class); + private final PeerPendingTransactionTracker tracker = + new PeerPendingTransactionTracker(pendingTransactions); + private final Hash hash1 = generator.transaction().getHash(); + private final Hash hash2 = generator.transaction().getHash(); + private final Hash hash3 = generator.transaction().getHash(); + + @Before + public void setUp() { + Transaction tx = mock(Transaction.class); + when(pendingTransactions.getTransactionByHash(any())).thenReturn(Optional.of(tx)); + } + + @Test + public void shouldTrackTransactionsToSendToPeer() { + tracker.addToPeerSendQueue(ethPeer1, hash1); + tracker.addToPeerSendQueue(ethPeer1, hash2); + tracker.addToPeerSendQueue(ethPeer2, hash3); + + assertThat(tracker.getEthPeersWithUnsentTransactions()).containsOnly(ethPeer1, ethPeer2); + assertThat(tracker.claimTransactionsToSendToPeer(ethPeer1)).containsOnly(hash1, hash2); + assertThat(tracker.claimTransactionsToSendToPeer(ethPeer2)).containsOnly(hash3); + } + + @Test + public void shouldExcludeAlreadySeenTransactionsFromTransactionsToSend() { + tracker.markTransactionsHashesAsSeen(ethPeer1, ImmutableSet.of(hash2)); + + tracker.addToPeerSendQueue(ethPeer1, hash1); + tracker.addToPeerSendQueue(ethPeer1, hash2); + tracker.addToPeerSendQueue(ethPeer2, hash3); + + assertThat(tracker.getEthPeersWithUnsentTransactions()).containsOnly(ethPeer1, ethPeer2); + assertThat(tracker.claimTransactionsToSendToPeer(ethPeer1)).containsOnly(hash1); + assertThat(tracker.claimTransactionsToSendToPeer(ethPeer2)).containsOnly(hash3); + } + + @Test + public void shouldExcludeAlreadySeenTransactionsAsACollectionFromTransactionsToSend() { + tracker.markTransactionsHashesAsSeen(ethPeer1, ImmutableSet.of(hash1, hash2)); + + tracker.addToPeerSendQueue(ethPeer1, hash1); + tracker.addToPeerSendQueue(ethPeer1, hash2); + tracker.addToPeerSendQueue(ethPeer2, hash3); + + assertThat(tracker.getEthPeersWithUnsentTransactions()).containsOnly(ethPeer2); + assertThat(tracker.claimTransactionsToSendToPeer(ethPeer1)).isEmpty(); + assertThat(tracker.claimTransactionsToSendToPeer(ethPeer2)).containsOnly(hash3); + } + + @Test + public void shouldClearDataWhenPeerDisconnects() { + tracker.markTransactionsHashesAsSeen(ethPeer1, ImmutableSet.of(hash3)); + + tracker.addToPeerSendQueue(ethPeer1, hash2); + tracker.addToPeerSendQueue(ethPeer2, hash3); + + tracker.onDisconnect(ethPeer1); + + assertThat(tracker.getEthPeersWithUnsentTransactions()).containsOnly(ethPeer2); + + // Should have cleared data that ethPeer1 has already seen transaction1 + tracker.addToPeerSendQueue(ethPeer1, hash1); + + assertThat(tracker.getEthPeersWithUnsentTransactions()).containsOnly(ethPeer1, ethPeer2); + assertThat(tracker.claimTransactionsToSendToPeer(ethPeer1)).containsOnly(hash1); + assertThat(tracker.claimTransactionsToSendToPeer(ethPeer2)).containsOnly(hash3); + } +} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionsMessageProcessorTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionsMessageProcessorTest.java new file mode 100644 index 0000000000..2e9a6ebb33 --- /dev/null +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionsMessageProcessorTest.java @@ -0,0 +1,97 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.transactions; + +import static java.time.Duration.ofMillis; +import static java.time.Duration.ofMinutes; +import static java.time.Instant.now; +import static java.util.Arrays.asList; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +import org.hyperledger.besu.ethereum.core.BlockDataGenerator; +import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.ethereum.eth.manager.EthPeer; +import org.hyperledger.besu.ethereum.eth.messages.NewPooledTransactionHashesMessage; +import org.hyperledger.besu.plugin.services.metrics.Counter; + +import java.util.Arrays; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class PendingTransactionsMessageProcessorTest { + + @Mock private TransactionPool transactionPool; + @Mock private PeerPendingTransactionTracker transactionTracker; + @Mock private Counter totalSkippedTransactionsMessageCounter; + @Mock private EthPeer peer1; + @InjectMocks private PendingTransactionsMessageProcessor messageHandler; + + private final BlockDataGenerator generator = new BlockDataGenerator(); + private final Hash hash1 = generator.transaction().getHash(); + private final Hash hash2 = generator.transaction().getHash(); + private final Hash hash3 = generator.transaction().getHash(); + + @Test + public void shouldMarkAllReceivedTransactionsAsSeen() { + messageHandler.processNewPooledTransactionHashesMessage( + peer1, + NewPooledTransactionHashesMessage.create(asList(hash1, hash2, hash3)), + now(), + ofMinutes(1)); + + verify(transactionTracker) + .markTransactionsHashesAsSeen(peer1, Arrays.asList(hash1, hash2, hash3)); + } + + @Test + public void shouldAddInitiatedRequestingTransactions() { + messageHandler.processNewPooledTransactionHashesMessage( + peer1, + NewPooledTransactionHashesMessage.create(asList(hash1, hash2, hash3)), + now(), + ofMinutes(1)); + verify(transactionPool).addTransactionHashes(hash1); + verify(transactionPool).addTransactionHashes(hash2); + verify(transactionPool).addTransactionHashes(hash3); + } + + @Test + public void shouldNotMarkReceivedExpiredTransactionsAsSeen() { + messageHandler.processNewPooledTransactionHashesMessage( + peer1, + NewPooledTransactionHashesMessage.create(asList(hash1, hash2, hash3)), + now().minus(ofMinutes(1)), + ofMillis(1)); + verifyZeroInteractions(transactionTracker); + verify(totalSkippedTransactionsMessageCounter).inc(1); + } + + @Test + public void shouldNotAddReceivedTransactionsToTransactionPoolIfExpired() { + messageHandler.processNewPooledTransactionHashesMessage( + peer1, + NewPooledTransactionHashesMessage.create(asList(hash1, hash2, hash3)), + now().minus(ofMinutes(1)), + ofMillis(1)); + verifyZeroInteractions(transactionPool); + verify(totalSkippedTransactionsMessageCounter).inc(1); + } +} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionsMessageSenderTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionsMessageSenderTest.java new file mode 100644 index 0000000000..49a5604479 --- /dev/null +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionsMessageSenderTest.java @@ -0,0 +1,127 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.transactions; + +import static com.google.common.collect.Sets.newHashSet; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.ethereum.core.BlockDataGenerator; +import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.eth.manager.EthPeer; +import org.hyperledger.besu.ethereum.eth.messages.EthPV65; +import org.hyperledger.besu.ethereum.eth.messages.NewPooledTransactionHashesMessage; +import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; + +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import com.google.common.collect.Sets; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +public class PendingTransactionsMessageSenderTest { + + private final EthPeer peer1 = mock(EthPeer.class); + private final EthPeer peer2 = mock(EthPeer.class); + + private final BlockDataGenerator generator = new BlockDataGenerator(); + private final Hash transaction1 = generator.transaction().getHash(); + private final Hash transaction2 = generator.transaction().getHash(); + private final Hash transaction3 = generator.transaction().getHash(); + + private final PendingTransactions pendingTransactions = mock(PendingTransactions.class); + private final PeerPendingTransactionTracker transactionTracker = + new PeerPendingTransactionTracker(pendingTransactions); + private final PendingTransactionsMessageSender messageSender = + new PendingTransactionsMessageSender(transactionTracker); + + @Before + public void setUp() { + Transaction tx = mock(Transaction.class); + when(pendingTransactions.getTransactionByHash(any())).thenReturn(Optional.of(tx)); + } + + @Test + public void shouldSendPendingTransactionsToEachPeer() throws Exception { + + transactionTracker.addToPeerSendQueue(peer1, transaction1); + transactionTracker.addToPeerSendQueue(peer1, transaction2); + transactionTracker.addToPeerSendQueue(peer2, transaction3); + + messageSender.sendTransactionsToPeers(); + + verify(peer1).send(transactionsMessageContaining(transaction1, transaction2)); + verify(peer2).send(transactionsMessageContaining(transaction3)); + verifyNoMoreInteractions(peer1, peer2); + } + + @Test + public void shouldSendTransactionsInBatchesWithLimit() throws Exception { + final Set transactions = + generator.transactions(6000).stream().map(Transaction::getHash).collect(Collectors.toSet()); + + transactions.forEach(transaction -> transactionTracker.addToPeerSendQueue(peer1, transaction)); + + messageSender.sendTransactionsToPeers(); + final ArgumentCaptor messageDataArgumentCaptor = + ArgumentCaptor.forClass(MessageData.class); + verify(peer1, times(2)).send(messageDataArgumentCaptor.capture()); + + final List sentMessages = messageDataArgumentCaptor.getAllValues(); + + assertThat(sentMessages).hasSize(2); + assertThat(sentMessages) + .allMatch(message -> message.getCode() == EthPV65.NEW_POOLED_TRANSACTION_HASHES); + final Set firstBatch = getTransactionsFromMessage(sentMessages.get(0)); + final Set secondBatch = getTransactionsFromMessage(sentMessages.get(1)); + + final int expectedFirstBatchSize = 4096, expectedSecondBatchSize = 1904, toleranceDelta = 0; + assertThat(firstBatch) + .hasSizeBetween( + expectedFirstBatchSize - toleranceDelta, expectedFirstBatchSize + toleranceDelta); + assertThat(secondBatch) + .hasSizeBetween( + expectedSecondBatchSize - toleranceDelta, expectedSecondBatchSize + toleranceDelta); + + assertThat(Sets.union(firstBatch, secondBatch)).isEqualTo(transactions); + } + + private MessageData transactionsMessageContaining(final Hash... transactions) { + return argThat( + message -> { + final Set actualSentTransactions = getTransactionsFromMessage(message); + final Set expectedTransactions = newHashSet(transactions); + return message.getCode() == EthPV65.NEW_POOLED_TRANSACTION_HASHES + && actualSentTransactions.equals(expectedTransactions); + }); + } + + private Set getTransactionsFromMessage(final MessageData message) { + final NewPooledTransactionHashesMessage transactionsMessage = + NewPooledTransactionHashesMessage.readFrom(message); + return newHashSet(transactionsMessage.pendingTransactions()); + } +} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionsTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionsTest.java index 231b88714c..2df5162216 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionsTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionsTest.java @@ -41,6 +41,7 @@ import org.junit.Test; public class PendingTransactionsTest { private static final int MAX_TRANSACTIONS = 5; + private static final int MAX_TRANSACTION_HASHES = 5; private static final KeyPair KEYS1 = KeyPair.generate(); private static final KeyPair KEYS2 = KeyPair.generate(); private static final String ADDED_COUNTER = "transactions_added_total"; @@ -55,6 +56,7 @@ public class PendingTransactionsTest { new PendingTransactions( TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, MAX_TRANSACTIONS, + MAX_TRANSACTION_HASHES, TestClock.fixed(), metricsSystem); private final Transaction transaction1 = createTransaction(2); @@ -551,7 +553,11 @@ public class PendingTransactionsTest { final int maxTransactionRetentionHours = 1; final PendingTransactions transactions = new PendingTransactions( - maxTransactionRetentionHours, MAX_TRANSACTIONS, clock, metricsSystem); + maxTransactionRetentionHours, + MAX_TRANSACTIONS, + MAX_TRANSACTION_HASHES, + clock, + metricsSystem); transactions.addRemoteTransaction(transaction1); assertThat(transactions.size()).isEqualTo(1); @@ -569,7 +575,11 @@ public class PendingTransactionsTest { final int maxTransactionRetentionHours = 1; final PendingTransactions transactions = new PendingTransactions( - maxTransactionRetentionHours, MAX_TRANSACTIONS, clock, metricsSystem); + maxTransactionRetentionHours, + MAX_TRANSACTIONS, + MAX_TRANSACTION_HASHES, + clock, + metricsSystem); transactions.addRemoteTransaction(transaction1); assertThat(transactions.size()).isEqualTo(1); clock.step(2L, ChronoUnit.HOURS); @@ -583,7 +593,11 @@ public class PendingTransactionsTest { final int maxTransactionRetentionHours = 2; final PendingTransactions transactions = new PendingTransactions( - maxTransactionRetentionHours, MAX_TRANSACTIONS, clock, metricsSystem); + maxTransactionRetentionHours, + MAX_TRANSACTIONS, + MAX_TRANSACTION_HASHES, + clock, + metricsSystem); transactions.addRemoteTransaction(transaction1); assertThat(transactions.size()).isEqualTo(1); clock.step(3L, ChronoUnit.HOURS); 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 2ecaad8bff..0d4e54831b 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 @@ -34,7 +34,10 @@ import org.hyperledger.besu.ethereum.difficulty.fixed.FixedDifficultyProtocolSch import org.hyperledger.besu.ethereum.eth.EthProtocol; import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.manager.EthContext; +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.EthScheduler; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions; @@ -110,19 +113,41 @@ public class TestNode implements Closeable { genesisState.writeStateTo(worldStateArchive.getMutable()); final ProtocolContext protocolContext = new ProtocolContext<>(blockchain, worldStateArchive, null); + + final SyncState syncState = mock(SyncState.class); + when(syncState.isInSync(anyLong())).thenReturn(true); + + final EthMessages ethMessages = new EthMessages(); + + final EthPeers ethPeers = new EthPeers(EthProtocol.NAME, TestClock.fixed(), metricsSystem); + + final EthScheduler scheduler = new EthScheduler(1, 1, 1, metricsSystem); + final EthContext ethContext = new EthContext(ethPeers, ethMessages, scheduler); + + transactionPool = + TransactionPoolFactory.createTransactionPool( + protocolSchedule, + protocolContext, + ethContext, + TestClock.fixed(), + metricsSystem, + syncState, + Wei.ZERO, + TransactionPoolConfiguration.builder().build()); + final EthProtocolManager ethProtocolManager = new EthProtocolManager( blockchain, - worldStateArchive, BigInteger.ONE, + worldStateArchive, + transactionPool, + EthProtocolConfiguration.defaultConfig(), + ethPeers, + ethMessages, + ethContext, Collections.emptyList(), false, - 1, - 1, - 1, - TestClock.fixed(), - new NoOpMetricsSystem(), - EthProtocolConfiguration.defaultConfig()); + scheduler); final NetworkRunner networkRunner = NetworkRunner.builder() @@ -143,22 +168,6 @@ public class TestNode implements Closeable { network.subscribeDisconnect( (connection, reason, initiatedByPeer) -> disconnections.put(connection, reason)); - final EthContext ethContext = ethProtocolManager.ethContext(); - - final SyncState syncState = mock(SyncState.class); - when(syncState.isInSync(anyLong())).thenReturn(true); - - transactionPool = - TransactionPoolFactory.createTransactionPool( - protocolSchedule, - protocolContext, - ethContext, - TestClock.fixed(), - metricsSystem, - syncState, - Wei.ZERO, - TransactionPoolConfiguration.builder().build()); - networkRunner.start(); selfPeer = DefaultPeer.fromEnodeURL(network.getLocalEnode().get()); } 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 9d4fbf8f87..c2e4cf5597 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,11 +77,14 @@ import org.mockito.ArgumentCaptor; public class TransactionPoolTest { private static final int MAX_TRANSACTIONS = 5; + private static final int MAX_TRANSACTION_HASHES = 5; private static final KeyPair KEY_PAIR1 = KeyPair.generate(); private final PendingTransactionListener listener = mock(PendingTransactionListener.class); private final TransactionPool.TransactionBatchAddedListener batchAddedListener = mock(TransactionPool.TransactionBatchAddedListener.class); + private final TransactionPool.TransactionBatchAddedListener pendingBatchAddedListener = + mock(TransactionPool.TransactionBatchAddedListener.class); private final MetricsSystem metricsSystem = new NoOpMetricsSystem(); @SuppressWarnings("unchecked") @@ -97,6 +100,7 @@ public class TransactionPoolTest { new PendingTransactions( TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, MAX_TRANSACTIONS, + MAX_TRANSACTION_HASHES, TestClock.fixed(), metricsSystem); private final Transaction transaction1 = createTransaction(1); @@ -108,6 +112,7 @@ public class TransactionPoolTest { private SyncState syncState; private EthContext ethContext; private PeerTransactionTracker peerTransactionTracker; + private PeerPendingTransactionTracker peerPendingTransactionTracker; @Before public void setUp() { @@ -121,15 +126,18 @@ public class TransactionPoolTest { EthPeers ethPeers = mock(EthPeers.class); when(ethContext.getEthPeers()).thenReturn(ethPeers); peerTransactionTracker = mock(PeerTransactionTracker.class); + peerPendingTransactionTracker = mock(PeerPendingTransactionTracker.class); transactionPool = new TransactionPool( transactions, protocolSchedule, protocolContext, batchAddedListener, + pendingBatchAddedListener, syncState, ethContext, peerTransactionTracker, + peerPendingTransactionTracker, Wei.of(2), metricsSystem); blockchain.observeBlockAdded(transactionPool); @@ -380,9 +388,11 @@ public class TransactionPoolTest { protocolSchedule, protocolContext, batchAddedListener, + pendingBatchAddedListener, syncState, ethContext, peerTransactionTracker, + peerPendingTransactionTracker, Wei.ZERO, metricsSystem); @@ -391,6 +401,7 @@ public class TransactionPoolTest { transactionPool.addRemoteTransactions(singletonList(transaction1)); verify(pendingTransactions).containsTransaction(transaction1.getHash()); + verify(pendingTransactions).tryEvictTransactionHash(transaction1.getHash()); verifyZeroInteractions(transactionValidator); verifyNoMoreInteractions(pendingTransactions); } @@ -497,9 +508,11 @@ public class TransactionPoolTest { protocolSchedule, protocolContext, batchAddedListener, + pendingBatchAddedListener, syncState, ethContext, peerTransactionTracker, + peerPendingTransactionTracker, Wei.ZERO, metricsSystem); @@ -563,9 +576,11 @@ public class TransactionPoolTest { protocolSchedule, protocolContext, batchAddedListener, + pendingBatchAddedListener, syncState, ethContext, peerTransactionTracker, + peerPendingTransactionTracker, Wei.ZERO, metricsSystem); diff --git a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/RetestethContext.java b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/RetestethContext.java index 480af12ef3..f6552162b2 100644 --- a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/RetestethContext.java +++ b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/RetestethContext.java @@ -163,7 +163,7 @@ public class RetestethContext { final EthPeers ethPeers = new EthPeers("reteseth", retestethClock, metricsSystem); final SyncState syncState = new SyncState(blockchain, ethPeers); - ethScheduler = new EthScheduler(1, 1, 1, metricsSystem); + ethScheduler = new EthScheduler(1, 1, 1, 1, metricsSystem); final EthContext ethContext = new EthContext(ethPeers, new EthMessages(), ethScheduler); final TransactionPoolConfiguration transactionPoolConfiguration = diff --git a/util/src/main/java/org/hyperledger/besu/util/number/PositiveNumber.java b/util/src/main/java/org/hyperledger/besu/util/number/PositiveNumber.java index 061117ec1d..8d381fc1c2 100644 --- a/util/src/main/java/org/hyperledger/besu/util/number/PositiveNumber.java +++ b/util/src/main/java/org/hyperledger/besu/util/number/PositiveNumber.java @@ -67,4 +67,9 @@ public class PositiveNumber { public int hashCode() { return Objects.hash(value); } + + @Override + public String toString() { + return "+" + value; + } }