diff --git a/CHANGELOG.md b/CHANGELOG.md index 4278ca6580e..66239103c35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ - Add TLS/mTLS options and configure the GraphQL HTTP service[#7910](https://github.com/hyperledger/besu/pull/7910) - Update `eth_getLogs` to return a `Block not found` error when the requested block is not found. [#8290](https://github.com/hyperledger/besu/pull/8290) - Change `Invalid block, unable to parse RLP` RPC error message to `Invalid block param (block not found)` [#8328](https://github.com/hyperledger/besu/pull/8328) +- Support pending transaction score when saving and restoring txpool [#8363](https://github.com/hyperledger/besu/pull/8363) ### Bug fixes - Add missing RPC method `debug_accountRange` to `RpcMethod.java` so this method can be used with `--rpc-http-api-method-no-auth` [#8153](https://github.com/hyperledger/besu/issues/8153) diff --git a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinatorTest.java b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinatorTest.java index b05e2f89743..cc107e88400 100644 --- a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinatorTest.java +++ b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinatorTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.fail; import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryBlockchain; import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryWorldStateArchive; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MAX_SCORE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; @@ -1080,7 +1081,8 @@ private PendingTransaction createLocalTransaction(final long transactionNumber) .nonce(transactionNumber) .createTransaction(KEYS1), true, - true); + true, + MAX_SCORE); } private static BlockHeader mockBlockHeader() { diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlobSizeTransactionSelectorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlobSizeTransactionSelectorTest.java index 309343ac58b..473834e594b 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlobSizeTransactionSelectorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlobSizeTransactionSelectorTest.java @@ -16,6 +16,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MAX_SCORE; import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.BLOBS_FULL; import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.SELECTED; import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.TX_TOO_LARGE_FOR_REMAINING_BLOB_GAS; @@ -172,12 +173,12 @@ private void evaluateAndAssertNotSelected( private PendingTransaction createEIP1559PendingTransaction() { return PendingTransaction.newPendingTransaction( - createTransaction(TransactionType.EIP1559, 0), false, false); + createTransaction(TransactionType.EIP1559, 0), false, false, MAX_SCORE); } private PendingTransaction createBlobPendingTransaction(final int blobCount) { return PendingTransaction.newPendingTransaction( - createTransaction(TransactionType.BLOB, blobCount), false, false); + createTransaction(TransactionType.BLOB, blobCount), false, false, MAX_SCORE); } private Transaction createTransaction(final TransactionType type, final int blobCount) { diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlockSizeTransactionSelectorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlockSizeTransactionSelectorTest.java index 8463f5e0456..b867a9a2084 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlockSizeTransactionSelectorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlockSizeTransactionSelectorTest.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.blockcreation.txselection.selectors; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MAX_SCORE; import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.BLOCK_FULL; import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.BLOCK_OCCUPANCY_ABOVE_THRESHOLD; import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.SELECTED; @@ -247,7 +248,7 @@ private void evaluateAndAssertNotSelected( private PendingTransaction createPendingTransaction(final long gasLimit) { return PendingTransaction.newPendingTransaction( - createTransaction(TransactionType.EIP1559, gasLimit), false, false); + createTransaction(TransactionType.EIP1559, gasLimit), false, false, MAX_SCORE); } private Transaction createTransaction(final TransactionType type, final long gasLimit) { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransaction.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransaction.java index b0eeea81167..f16f0a5b59f 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransaction.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransaction.java @@ -48,7 +48,8 @@ */ public abstract class PendingTransaction implements org.hyperledger.besu.datatypes.PendingTransaction { - static final int NOT_INITIALIZED = -1; + public static final Byte MAX_SCORE = Byte.MAX_VALUE; + private static final int NOT_INITIALIZED = -1; private static final AtomicLong TRANSACTIONS_ADDED = new AtomicLong(); private final Transaction transaction; private final long addedAt; @@ -58,37 +59,42 @@ public abstract class PendingTransaction private int memorySize = NOT_INITIALIZED; private PendingTransaction( - final Transaction transaction, final long addedAt, final long sequence, final byte score) { + final Transaction transaction, final byte score, final long addedAt, final long sequence) { this.transaction = transaction; this.addedAt = addedAt; this.sequence = sequence; this.score = score; } - private PendingTransaction(final Transaction transaction, final long addedAt) { - this(transaction, addedAt, TRANSACTIONS_ADDED.getAndIncrement(), Byte.MAX_VALUE); + private PendingTransaction(final Transaction transaction, final byte score, final long addedAt) { + this(transaction, score, addedAt, TRANSACTIONS_ADDED.getAndIncrement()); } public static PendingTransaction newPendingTransaction( - final Transaction transaction, final boolean isLocal, final boolean hasPriority) { - return newPendingTransaction(transaction, isLocal, hasPriority, System.currentTimeMillis()); + final Transaction transaction, + final boolean isLocal, + final boolean hasPriority, + final byte score) { + return newPendingTransaction( + transaction, isLocal, hasPriority, score, System.currentTimeMillis()); } public static PendingTransaction newPendingTransaction( final Transaction transaction, final boolean isLocal, final boolean hasPriority, + final byte score, final long addedAt) { if (isLocal) { if (hasPriority) { - return new Local.Priority(transaction, addedAt); + return new Local.Priority(transaction, score, addedAt); } - return new Local(transaction, addedAt); + return new Local(transaction, score, addedAt); } if (hasPriority) { - return new Remote.Priority(transaction, addedAt); + return new Remote.Priority(transaction, score, addedAt); } - return new Remote(transaction, addedAt); + return new Remote(transaction, score, addedAt); } @Override @@ -311,16 +317,16 @@ public String toTraceLog() { public static class Local extends PendingTransaction { - public Local(final Transaction transaction, final long addedAt) { - super(transaction, addedAt); + public Local(final Transaction transaction, final byte score, final long addedAt) { + super(transaction, score, addedAt); } public Local(final Transaction transaction) { - this(transaction, System.currentTimeMillis()); + this(transaction, MAX_SCORE, System.currentTimeMillis()); } private Local(final long sequence, final byte score, final Transaction transaction) { - super(transaction, System.currentTimeMillis(), sequence, score); + super(transaction, score, System.currentTimeMillis(), sequence); } @Override @@ -340,11 +346,11 @@ public boolean hasPriority() { public static class Priority extends Local { public Priority(final Transaction transaction) { - this(transaction, System.currentTimeMillis()); + this(transaction, MAX_SCORE, System.currentTimeMillis()); } - public Priority(final Transaction transaction, final long addedAt) { - super(transaction, addedAt); + public Priority(final Transaction transaction, final byte score, final long addedAt) { + super(transaction, score, addedAt); } public Priority(final long sequence, final byte score, final Transaction transaction) { @@ -365,16 +371,16 @@ public boolean hasPriority() { public static class Remote extends PendingTransaction { - public Remote(final Transaction transaction, final long addedAt) { - super(transaction, addedAt); + public Remote(final Transaction transaction, final byte score, final long addedAt) { + super(transaction, score, addedAt); } public Remote(final Transaction transaction) { - this(transaction, System.currentTimeMillis()); + this(transaction, MAX_SCORE, System.currentTimeMillis()); } private Remote(final long sequence, final byte score, final Transaction transaction) { - super(transaction, System.currentTimeMillis(), sequence, score); + super(transaction, score, System.currentTimeMillis(), sequence); } @Override @@ -394,11 +400,11 @@ public boolean hasPriority() { public static class Priority extends Remote { public Priority(final Transaction transaction) { - this(transaction, System.currentTimeMillis()); + this(transaction, MAX_SCORE, System.currentTimeMillis()); } - public Priority(final Transaction transaction, final long addedAt) { - super(transaction, addedAt); + public Priority(final Transaction transaction, final byte score, final long addedAt) { + super(transaction, score, addedAt); } public Priority(final long sequence, final byte score, final Transaction transaction) { 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 a2310049e39..0ce61fa33ea 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 @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.ethereum.eth.transactions; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MAX_SCORE; import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.CHAIN_HEAD_NOT_AVAILABLE; import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.CHAIN_HEAD_WORLD_STATE_NOT_AVAILABLE; import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.INTERNAL_ERROR; @@ -32,6 +33,8 @@ import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.encoding.EncodingContext; +import org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; @@ -190,7 +193,7 @@ void handleConnect(final EthPeer peer) { public ValidationResult addTransactionViaApi( final Transaction transaction) { - final var result = addTransaction(transaction, true); + final var result = addTransaction(transaction, true, MAX_SCORE); if (result.isValid()) { localSenders.add(transaction.getSender()); transactionBroadcaster.onTransactionsAdded(List.of(transaction)); @@ -211,7 +214,7 @@ public Map> addRemoteTransactio Collectors.toMap( Transaction::getHash, transaction -> { - final var result = addTransaction(transaction, false); + final var result = addTransaction(transaction, false, MAX_SCORE); if (result.isValid()) { addedTransactions.add(transaction); } @@ -241,7 +244,7 @@ public Map> addRemoteTransactio } private ValidationResult addTransaction( - final Transaction transaction, final boolean isLocal) { + final Transaction transaction, final boolean isLocal, final byte score) { final boolean hasPriority = isPriorityTransaction(transaction, isLocal); @@ -261,7 +264,7 @@ private ValidationResult addTransaction( if (validationResult.result.isValid()) { final TransactionAddedResult status = pendingTransactions.addTransaction( - PendingTransaction.newPendingTransaction(transaction, isLocal, hasPriority), + PendingTransaction.newPendingTransaction(transaction, isLocal, hasPriority, score), validationResult.maybeAccount); if (status.isSuccess()) { LOG.atTrace() @@ -822,8 +825,10 @@ private void executeSaveToDisk(final PendingTransactions pendingTransactionsToSa .map( ptx -> { final BytesValueRLPOutput rlp = new BytesValueRLPOutput(); - ptx.getTransaction().writeTo(rlp); - return (ptx.isReceivedFromLocalSource() ? "l" : "r") + TransactionEncoder.encodeRLP( + ptx.getTransaction(), rlp, EncodingContext.POOLED_TRANSACTION); + return ptx.getScore() + + (ptx.isReceivedFromLocalSource() ? "l" : "r") + rlp.encoded().toBase64String(); }) .mapToInt( @@ -870,12 +875,16 @@ private void executeLoadFromDisk() { .takeWhile(unused -> !isCancelled.get()) .map( line -> { - final boolean isLocal = line.charAt(0) == 'l'; + final var scoreStr = parseScore(line); + final byte score = + scoreStr.isEmpty() ? MAX_SCORE : Byte.parseByte(scoreStr); + final boolean isLocal = line.charAt(scoreStr.length()) == 'l'; final Transaction tx = - Transaction.readFrom(Bytes.fromBase64String(line.substring(1))); + Transaction.readFrom( + Bytes.fromBase64String(line.substring(scoreStr.length() + 1))); final ValidationResult result = - addTransaction(tx, isLocal); + addTransaction(tx, isLocal, score); return result.isValid() ? "OK" : result.getInvalidReason().name(); }) .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); @@ -909,6 +918,15 @@ private void executeLoadFromDisk() { } } + private String parseScore(final String line) { + int i = 0; + final var sbScore = new StringBuilder(); + while ("1234567890-".indexOf(line.charAt(i)) >= 0) { + sbScore.append(line.charAt(i++)); + } + return sbScore.toString(); + } + private void removeProcessedLines(final File saveFile, final long processedLines) throws IOException { diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/AbstractTransactionPoolTestBase.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/AbstractTransactionPoolTestBase.java index f69232eec4e..8258631e8ca 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/AbstractTransactionPoolTestBase.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/AbstractTransactionPoolTestBase.java @@ -386,6 +386,18 @@ protected void givenTransactionIsValid(final Transaction transaction) { .thenReturn(valid()); } + protected void givenAllTransactionsAreValid() { + when(transactionValidatorFactory + .get() + .validate(any(), any(Optional.class), any(Optional.class), any())) + .thenReturn(valid()); + when(transactionValidatorFactory + .get() + .validateForSender( + any(), nullable(Account.class), any(TransactionValidationParams.class))) + .thenReturn(valid()); + } + protected abstract Block appendBlock( final Difficulty difficulty, final BlockHeader parentBlock, diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolSaveRestoreTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolSaveRestoreTest.java new file mode 100644 index 00000000000..18f1b53522e --- /dev/null +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolSaveRestoreTest.java @@ -0,0 +1,275 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.transactions; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.core.ExecutionContextTestFixture; +import org.hyperledger.besu.ethereum.core.MiningConfiguration; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.TransactionTestFixture; +import org.hyperledger.besu.ethereum.core.encoding.EncodingContext; +import org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder; +import org.hyperledger.besu.ethereum.eth.transactions.layered.BaseFeePrioritizedTransactions; +import org.hyperledger.besu.ethereum.eth.transactions.layered.EndLayer; +import org.hyperledger.besu.ethereum.eth.transactions.layered.LayeredPendingTransactions; +import org.hyperledger.besu.ethereum.eth.transactions.layered.ReadyTransactions; +import org.hyperledger.besu.ethereum.eth.transactions.layered.SparseTransactions; +import org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer; +import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.BiFunction; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class TransactionPoolSaveRestoreTest extends AbstractTransactionPoolTestBase { + @TempDir static Path tempDir; + Path saveFilePath; + + @BeforeEach + public void setup() { + saveFilePath = tempDir.resolve("txpool.dump"); + } + + @AfterEach + public void cleanup() { + saveFilePath.toFile().delete(); + } + + @Override + protected PendingTransactions createPendingTransactions( + final TransactionPoolConfiguration poolConfig, + final BiFunction + transactionReplacementTester) { + + final var txPoolMetrics = new TransactionPoolMetrics(metricsSystem); + final TransactionsLayer sparseLayer = + new SparseTransactions( + poolConfig, + ethScheduler, + new EndLayer(txPoolMetrics), + txPoolMetrics, + transactionReplacementTester, + new BlobCache()); + final TransactionsLayer readyLayer = + new ReadyTransactions( + poolConfig, + ethScheduler, + sparseLayer, + txPoolMetrics, + transactionReplacementTester, + new BlobCache()); + return new LayeredPendingTransactions( + poolConfig, + new BaseFeePrioritizedTransactions( + poolConfig, + protocolContext.getBlockchain()::getChainHeadHeader, + ethScheduler, + readyLayer, + txPoolMetrics, + transactionReplacementTester, + FeeMarket.london(0L), + new BlobCache(), + MiningConfiguration.newDefault()), + ethScheduler); + } + + @Override + protected ExecutionContextTestFixture createExecutionContextTestFixture() { + return createExecutionContextTestFixtureBaseFeeMarket(); + } + + @Override + protected FeeMarket getFeeMarket() { + return FeeMarket.london(0L, Optional.of(BASE_FEE_FLOOR)); + } + + @Override + protected Block appendBlock( + final Difficulty difficulty, + final BlockHeader parentBlock, + final Transaction... transactionsToAdd) { + return appendBlockBaseFeeMarket(difficulty, parentBlock, transactionsToAdd); + } + + @Override + protected TransactionTestFixture createBaseTransaction(final int nonce) { + return createBaseTransactionBaseFeeMarket(nonce); + } + + @Override + protected Transaction createTransaction(final int nonce, final Wei maxPrice) { + return createTransactionBaseFeeMarket(nonce, maxPrice); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void localTransactionIsSavedAndRestored(final boolean noLocalPriority) + throws ExecutionException, InterruptedException, TimeoutException, IOException { + // create a txpool with save and restore enabled + this.transactionPool = + createTransactionPool( + b -> + b.noLocalPriority(noLocalPriority) + .enableSaveRestore(true) + .saveFile(saveFilePath.toFile())); + + final Transaction transaction = createTransaction(noLocalPriority ? 0 : 1); + + givenTransactionIsValid(transaction); + + addAndAssertTransactionViaApiValid(transaction, noLocalPriority); + + // disabling the txpool, forces a save to file + transactionPool.setDisabled().get(10, TimeUnit.SECONDS); + + // after being disabled the txpool must be empty + assertThat(transactionPool.getPendingTransactions()).isEmpty(); + + final var savedContent = Files.readString(saveFilePath, StandardCharsets.US_ASCII); + + assertThat(savedContent).isEqualToIgnoringNewLines("127l" + transaction2Base64(transaction)); + + // re-enabling the txpool restores from file + transactionPool.setEnabled().get(10, TimeUnit.SECONDS); + + assertThat(transactionPool.getPendingTransactions()).size().isEqualTo(1); + + final var restoredPendingTx = transactionPool.getPendingTransactions().iterator().next(); + + assertThat(restoredPendingTx.getTransaction()).isEqualTo(transaction); + assertThat(restoredPendingTx.isReceivedFromLocalSource()).isTrue(); + assertThat(restoredPendingTx.hasPriority()).isNotEqualTo(noLocalPriority); + } + + @Test + public void remoteTransactionIsSavedAndRestored() + throws ExecutionException, InterruptedException, TimeoutException, IOException { + // create a txpool with save and restore enabled + this.transactionPool = + createTransactionPool(b -> b.enableSaveRestore(true).saveFile(saveFilePath.toFile())); + + final Transaction transaction = createTransaction(2); + + givenTransactionIsValid(transaction); + + addAndAssertRemoteTransactionsValid(transaction); + + // disabling the txpool, forces a save to file + transactionPool.setDisabled().get(10, TimeUnit.SECONDS); + + // after being disabled the txpool must be empty + assertThat(transactionPool.getPendingTransactions()).isEmpty(); + + final var savedContent = Files.readString(saveFilePath, StandardCharsets.US_ASCII); + + assertThat(savedContent).isEqualToIgnoringNewLines("127r" + transaction2Base64(transaction)); + + // re-enabling the txpool restores from file + transactionPool.setEnabled().get(10, TimeUnit.SECONDS); + + assertThat(transactionPool.getPendingTransactions()).size().isEqualTo(1); + + final var restoredPendingTx = transactionPool.getPendingTransactions().iterator().next(); + + assertThat(restoredPendingTx.getTransaction()).isEqualTo(transaction); + assertThat(restoredPendingTx.isReceivedFromLocalSource()).isFalse(); + } + + @Test + public void dumpFileWithoutScoreIsRestored() throws IOException { + + // create a save file with one local and one remote tx, both without score + final var noScoreContent = + """ + luFoC+FcBgIID6IITiIcf////////gASAwAGga1337/7O7cp7jaMTu9X230+6mLJciebaO5nrsgDRp1CgA5MCvzfmS4H3NqF0DIxJGl8atRTkKmFwLMZgPpkVTqQ= + ruFoC+FcBgIID6IITiIcf////////gASAwAGglm0VMcNQmOS0aE5CJP1Lm7eBbFQIRvmwgUcfEka9sVagYWy/2d2tJHojo2smAIJgwLbud9Dr+f1lbxo1dSOBfmE= + """; + + Files.writeString(saveFilePath, noScoreContent); + + givenAllTransactionsAreValid(); + + // create a txpool with save and restore enabled + this.transactionPool = + createTransactionPool(b -> b.enableSaveRestore(true).saveFile(saveFilePath.toFile())); + + await().until(() -> transactionPool.getPendingTransactions().size() == 2); + + assertThat(transactionPool.getPendingTransactions()) + .map(PendingTransaction::getScore) + .allMatch(score -> score == Byte.MAX_VALUE); + assertThat(transactionPool.getPendingTransactions()) + .map(PendingTransaction::isReceivedFromLocalSource) + .filteredOn(Boolean::booleanValue) + .hasSize(1); + } + + @Test + public void dumpFileWithLowestScoreTxIsRestored() throws IOException { + + // create a save file with one remote tx with the lowest score + final var noScoreContent = + """ + -128ruFoC+FcBgIID6IITiIcf////////gASAwAGga1337/7O7cp7jaMTu9X230+6mLJciebaO5nrsgDRp1CgA5MCvzfmS4H3NqF0DIxJGl8atRTkKmFwLMZgPpkVTqQ= + """; + + Files.writeString(saveFilePath, noScoreContent); + + givenAllTransactionsAreValid(); + + // create a txpool with save and restore enabled + this.transactionPool = + createTransactionPool(b -> b.enableSaveRestore(true).saveFile(saveFilePath.toFile())); + + await().until(() -> transactionPool.getPendingTransactions().size() == 1); + + assertThat(transactionPool.getPendingTransactions()) + .map(PendingTransaction::getScore) + .allMatch(score -> score == Byte.MIN_VALUE); + assertThat(transactionPool.getPendingTransactions()) + .map(PendingTransaction::isReceivedFromLocalSource) + .first() + .isEqualTo(false); + } + + private String transaction2Base64(final Transaction transaction) { + final BytesValueRLPOutput rlp = new BytesValueRLPOutput(); + TransactionEncoder.encodeRLP(transaction, rlp, EncodingContext.POOLED_TRANSACTION); + return rlp.encoded().toBase64String(); + } +} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseTransactionPoolTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseTransactionPoolTest.java index 2d575b0aa8b..92f4905f3dc 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseTransactionPoolTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseTransactionPoolTest.java @@ -16,6 +16,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.ethereum.core.TransactionTestFixture.createSignedCodeDelegation; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MAX_SCORE; import org.hyperledger.besu.crypto.KeyPair; import org.hyperledger.besu.crypto.SignatureAlgorithm; @@ -253,7 +254,7 @@ protected PendingTransaction createRemotePendingTransaction(final Transaction tr protected PendingTransaction createRemotePendingTransaction( final Transaction transaction, final boolean hasPriority) { - return PendingTransaction.newPendingTransaction(transaction, false, hasPriority); + return PendingTransaction.newPendingTransaction(transaction, false, hasPriority, MAX_SCORE); } protected PendingTransaction createLocalPendingTransaction(final Transaction transaction) { diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReplayTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReplayTest.java index 760591a87cf..c2d13e93818 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReplayTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReplayTest.java @@ -16,6 +16,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MAX_SCORE; import static org.hyperledger.besu.ethereum.eth.transactions.layered.LayeredRemovalReason.PoolRemovalReason.INVALIDATED; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -307,7 +308,7 @@ private void processTransaction( } assertThat( pendingTransactions.addTransaction( - PendingTransaction.newPendingTransaction(tx, false, false), + PendingTransaction.newPendingTransaction(tx, false, false, MAX_SCORE), Optional.of(mockAccount))) .isNotEqualTo(TransactionAddedResult.INTERNAL_ERROR); if (tx.getSender().equals(senderToLog)) { diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractPendingTransactionsTestBase.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractPendingTransactionsTestBase.java index 2cce07ba1e6..c5cbd113667 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractPendingTransactionsTestBase.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractPendingTransactionsTestBase.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.eth.transactions.sorter; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MAX_SCORE; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.ADDED; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.ALREADY_KNOWN; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.REJECTED_UNDERPRICED_REPLACEMENT; @@ -762,15 +763,15 @@ protected Transaction createZeroGasPriceTransactionSender2(final long transactio private PendingTransaction createRemotePendingTransaction( final Transaction transaction, final long addedAt) { - return PendingTransaction.newPendingTransaction(transaction, false, false, addedAt); + return PendingTransaction.newPendingTransaction(transaction, false, false, MAX_SCORE, addedAt); } private PendingTransaction createRemotePendingTransaction(final Transaction transaction) { - return PendingTransaction.newPendingTransaction(transaction, false, false); + return PendingTransaction.newPendingTransaction(transaction, false, false, MAX_SCORE); } private PendingTransaction createLocalPendingTransaction(final Transaction transaction) { - return PendingTransaction.newPendingTransaction(transaction, true, true); + return PendingTransaction.newPendingTransaction(transaction, true, true, MAX_SCORE); } @Test