|
18 | 18 | import static org.assertj.core.api.Assertions.entry; |
19 | 19 | import static org.awaitility.Awaitility.await; |
20 | 20 | import static org.hyperledger.besu.ethereum.core.MiningParameters.DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME; |
| 21 | +import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE; |
21 | 22 | import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.BLOCK_SELECTION_TIMEOUT; |
22 | 23 | import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.PRIORITY_FEE_PER_GAS_BELOW_CURRENT_MIN; |
23 | 24 | import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.SELECTED; |
|
90 | 91 | import java.time.Instant; |
91 | 92 | import java.util.ArrayList; |
92 | 93 | import java.util.Arrays; |
| 94 | +import java.util.HashMap; |
93 | 95 | import java.util.List; |
94 | 96 | import java.util.Optional; |
95 | 97 | import java.util.concurrent.CompletableFuture; |
@@ -1026,6 +1028,152 @@ private void internalBlockSelectionTimeoutSimulation( |
1026 | 1028 | .isEqualTo(isLongProcessingTxDropped ? true : false); |
1027 | 1029 | } |
1028 | 1030 |
|
| 1031 | + @ParameterizedTest |
| 1032 | + @MethodSource("subsetOfPendingTransactionsIncludedWhenTxSelectionMaxTimeIsOver") |
| 1033 | + public void subsetOfInvalidPendingTransactionsIncludedWhenTxSelectionMaxTimeIsOver( |
| 1034 | + final boolean isPoa, |
| 1035 | + final boolean preProcessingTooLate, |
| 1036 | + final boolean processingTooLate, |
| 1037 | + final boolean postProcessingTooLate) { |
| 1038 | + |
| 1039 | + internalBlockSelectionTimeoutSimulationInvalidTxs( |
| 1040 | + isPoa, |
| 1041 | + preProcessingTooLate, |
| 1042 | + processingTooLate, |
| 1043 | + postProcessingTooLate, |
| 1044 | + 500, |
| 1045 | + BLOCK_SELECTION_TIMEOUT, |
| 1046 | + false, |
| 1047 | + UPFRONT_COST_EXCEEDS_BALANCE); |
| 1048 | + } |
| 1049 | + |
| 1050 | + @ParameterizedTest |
| 1051 | + @MethodSource("subsetOfPendingTransactionsIncludedWhenTxSelectionMaxTimeIsOver") |
| 1052 | + public void invalidPendingTransactionsThatTakesTooLongToEvaluateIsDroppedFromThePool( |
| 1053 | + final boolean isPoa, |
| 1054 | + final boolean preProcessingTooLate, |
| 1055 | + final boolean processingTooLate, |
| 1056 | + final boolean postProcessingTooLate) { |
| 1057 | + |
| 1058 | + internalBlockSelectionTimeoutSimulationInvalidTxs( |
| 1059 | + isPoa, |
| 1060 | + preProcessingTooLate, |
| 1061 | + processingTooLate, |
| 1062 | + postProcessingTooLate, |
| 1063 | + 900, |
| 1064 | + TX_EVALUATION_TOO_LONG, |
| 1065 | + true, |
| 1066 | + UPFRONT_COST_EXCEEDS_BALANCE); |
| 1067 | + } |
| 1068 | + |
| 1069 | + private void internalBlockSelectionTimeoutSimulationInvalidTxs( |
| 1070 | + final boolean isPoa, |
| 1071 | + final boolean preProcessingTooLate, |
| 1072 | + final boolean processingTooLate, |
| 1073 | + final boolean postProcessingTooLate, |
| 1074 | + final long longProcessingTxTime, |
| 1075 | + final TransactionSelectionResult longProcessingTxResult, |
| 1076 | + final boolean isLongProcessingTxDropped, |
| 1077 | + final TransactionInvalidReason txInvalidReason) { |
| 1078 | + |
| 1079 | + final int txCount = 3; |
| 1080 | + final long fastProcessingTxTime = 200; |
| 1081 | + final var invalidSelectionResult = TransactionSelectionResult.invalid(txInvalidReason.name()); |
| 1082 | + |
| 1083 | + final Supplier<Answer<TransactionSelectionResult>> inTime = () -> invocation -> SELECTED; |
| 1084 | + |
| 1085 | + final BiFunction<Transaction, Long, Answer<TransactionSelectionResult>> tooLate = |
| 1086 | + (p, t) -> |
| 1087 | + invocation -> { |
| 1088 | + final org.hyperledger.besu.ethereum.blockcreation.txselection |
| 1089 | + .TransactionEvaluationContext |
| 1090 | + ctx = invocation.getArgument(0); |
| 1091 | + if (ctx.getTransaction().equals(p)) { |
| 1092 | + Thread.sleep(t); |
| 1093 | + } else { |
| 1094 | + Thread.sleep(fastProcessingTxTime); |
| 1095 | + } |
| 1096 | + return invalidSelectionResult; |
| 1097 | + }; |
| 1098 | + |
| 1099 | + final ProcessableBlockHeader blockHeader = createBlock(301_000); |
| 1100 | + final Address miningBeneficiary = AddressHelpers.ofValue(1); |
| 1101 | + final int poaGenesisBlockPeriod = 1; |
| 1102 | + final int blockTxsSelectionMaxTime = 750; |
| 1103 | + |
| 1104 | + final List<Transaction> transactionsToInject = new ArrayList<>(txCount); |
| 1105 | + for (int i = 0; i < txCount - 1; i++) { |
| 1106 | + final Transaction tx = createTransaction(i, Wei.of(7), 100_000); |
| 1107 | + transactionsToInject.add(tx); |
| 1108 | + if (processingTooLate) { |
| 1109 | + ensureTransactionIsInvalid(tx, txInvalidReason, fastProcessingTxTime); |
| 1110 | + } else { |
| 1111 | + ensureTransactionIsValid(tx); |
| 1112 | + } |
| 1113 | + } |
| 1114 | + |
| 1115 | + final Transaction lateTx = createTransaction(2, Wei.of(7), 100_000); |
| 1116 | + transactionsToInject.add(lateTx); |
| 1117 | + if (processingTooLate) { |
| 1118 | + ensureTransactionIsInvalid(lateTx, txInvalidReason, longProcessingTxTime); |
| 1119 | + } else { |
| 1120 | + ensureTransactionIsValid(lateTx); |
| 1121 | + } |
| 1122 | + |
| 1123 | + PluginTransactionSelector transactionSelector = mock(PluginTransactionSelector.class); |
| 1124 | + when(transactionSelector.evaluateTransactionPreProcessing(any())) |
| 1125 | + .thenAnswer( |
| 1126 | + preProcessingTooLate ? tooLate.apply(lateTx, longProcessingTxTime) : inTime.get()); |
| 1127 | + |
| 1128 | + when(transactionSelector.evaluateTransactionPostProcessing(any(), any())) |
| 1129 | + .thenAnswer( |
| 1130 | + postProcessingTooLate ? tooLate.apply(lateTx, longProcessingTxTime) : inTime.get()); |
| 1131 | + |
| 1132 | + final PluginTransactionSelectorFactory transactionSelectorFactory = |
| 1133 | + mock(PluginTransactionSelectorFactory.class); |
| 1134 | + when(transactionSelectorFactory.create()).thenReturn(transactionSelector); |
| 1135 | + |
| 1136 | + final BlockTransactionSelector selector = |
| 1137 | + createBlockSelectorAndSetupTxPool( |
| 1138 | + isPoa |
| 1139 | + ? createMiningParameters( |
| 1140 | + Wei.ZERO, |
| 1141 | + MIN_OCCUPANCY_100_PERCENT, |
| 1142 | + poaGenesisBlockPeriod, |
| 1143 | + PositiveNumber.fromInt(75)) |
| 1144 | + : createMiningParameters( |
| 1145 | + Wei.ZERO, |
| 1146 | + MIN_OCCUPANCY_100_PERCENT, |
| 1147 | + PositiveNumber.fromInt(blockTxsSelectionMaxTime)), |
| 1148 | + transactionProcessor, |
| 1149 | + blockHeader, |
| 1150 | + miningBeneficiary, |
| 1151 | + Wei.ZERO, |
| 1152 | + transactionSelectorFactory); |
| 1153 | + |
| 1154 | + transactionPool.addRemoteTransactions(transactionsToInject); |
| 1155 | + |
| 1156 | + final TransactionSelectionResults results = selector.buildTransactionListForBlock(); |
| 1157 | + |
| 1158 | + // no tx is selected since all are invalid |
| 1159 | + assertThat(results.getSelectedTransactions()).isEmpty(); |
| 1160 | + |
| 1161 | + // all txs are not selected so wait until all are evaluated |
| 1162 | + // before checking the results |
| 1163 | + await().until(() -> results.getNotSelectedTransactions().size() == transactionsToInject.size()); |
| 1164 | + final var expectedEntries = new HashMap<Transaction, TransactionSelectionResult>(); |
| 1165 | + for (int i = 0; i < txCount - 1; i++) { |
| 1166 | + expectedEntries.put( |
| 1167 | + transactionsToInject.get(i), TransactionSelectionResult.invalid(txInvalidReason.name())); |
| 1168 | + } |
| 1169 | + expectedEntries.put(lateTx, longProcessingTxResult); |
| 1170 | + assertThat(results.getNotSelectedTransactions()) |
| 1171 | + .containsExactlyInAnyOrderEntriesOf(expectedEntries); |
| 1172 | + |
| 1173 | + assertThat(transactionPool.getTransactionByHash(lateTx.getHash()).isEmpty()) |
| 1174 | + .isEqualTo(isLongProcessingTxDropped ? true : false); |
| 1175 | + } |
| 1176 | + |
1029 | 1177 | private static Stream<Arguments> |
1030 | 1178 | subsetOfPendingTransactionsIncludedWhenTxSelectionMaxTimeIsOver() { |
1031 | 1179 |
|
@@ -1170,9 +1318,22 @@ protected void ensureTransactionIsValid( |
1170 | 1318 |
|
1171 | 1319 | protected void ensureTransactionIsInvalid( |
1172 | 1320 | final Transaction tx, final TransactionInvalidReason invalidReason) { |
| 1321 | + ensureTransactionIsInvalid(tx, invalidReason, 0); |
| 1322 | + } |
| 1323 | + |
| 1324 | + protected void ensureTransactionIsInvalid( |
| 1325 | + final Transaction tx, |
| 1326 | + final TransactionInvalidReason invalidReason, |
| 1327 | + final long processingTime) { |
1173 | 1328 | when(transactionProcessor.processTransaction( |
1174 | 1329 | any(), any(), any(), eq(tx), any(), any(), any(), anyBoolean(), any(), any())) |
1175 | | - .thenReturn(TransactionProcessingResult.invalid(ValidationResult.invalid(invalidReason))); |
| 1330 | + .thenAnswer( |
| 1331 | + invocation -> { |
| 1332 | + if (processingTime > 0) { |
| 1333 | + Thread.sleep(processingTime); |
| 1334 | + } |
| 1335 | + return TransactionProcessingResult.invalid(ValidationResult.invalid(invalidReason)); |
| 1336 | + }); |
1176 | 1337 | } |
1177 | 1338 |
|
1178 | 1339 | private BlockHeader blockHeader(final long number) { |
|
0 commit comments