Merge branch 'main' into 7311-add-peertask-foundation-code

pull/7628/head
Matilda-Clerke 2 months ago committed by GitHub
commit b7c0c95684
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      .github/workflows/release.yml
  2. 5
      CHANGELOG.md
  3. 2
      MAINTAINERS.md
  4. 10
      SUPPORT.md
  5. 64
      besu/src/test/java/org/hyperledger/besu/cli/subcommands/operator/OperatorSubCommandTest.java
  6. 9
      besu/src/test/resources/operator/config_generate_keys.json
  7. 9
      besu/src/test/resources/operator/config_generate_keys_ec_invalid.json
  8. 9
      besu/src/test/resources/operator/config_generate_keys_secp256r1.json
  9. 9
      besu/src/test/resources/operator/config_import_keys.json
  10. 9
      besu/src/test/resources/operator/config_import_keys_invalid_keys.json
  11. 9
      besu/src/test/resources/operator/config_import_keys_qbft.json
  12. 9
      besu/src/test/resources/operator/config_import_keys_secp256r1.json
  13. 9
      besu/src/test/resources/operator/config_import_keys_secp256r1_invalid_keys.json
  14. 5
      besu/src/test/resources/operator/config_no_config_section.json
  15. 8
      config/src/main/java/org/hyperledger/besu/config/GenesisReader.java
  16. 20
      config/src/main/java/org/hyperledger/besu/config/JsonUtil.java
  17. 16
      config/src/test/java/org/hyperledger/besu/config/GenesisReaderTest.java
  18. 30
      config/src/test/java/org/hyperledger/besu/config/JsonUtilTest.java
  19. 4
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetClientVersionV1.java
  20. 95
      ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockTransactionSelector.java
  21. 143
      ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/InterruptibleOperationTracer.java
  22. 3
      ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/ProcessingResultTransactionSelector.java
  23. 52
      ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockTransactionSelectorTest.java
  24. 6
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java
  25. 1
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java
  26. 4
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java
  27. 4
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactions.java
  28. 2
      plugin-api/build.gradle
  29. 7
      plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionSelectionResult.java

@ -14,8 +14,9 @@ jobs:
steps:
- name: Pre-process Release Name
id: pre_process_release_name
env:
RELEASE_NAME: "${{ github.event.release.name }}"
run: |
RELEASE_NAME="${{ github.event.release.name }}"
# strip all whitespace
RELEASE_NAME="${RELEASE_NAME//[[:space:]]/}"
if [[ ! "$RELEASE_NAME" =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?(-.*)?$ ]]; then

@ -1,8 +1,6 @@
# Changelog
## [Unreleased]
- Add configuration of Consolidation Request Contract Address via genesis configuration [#7647](https://github.com/hyperledger/besu/pull/7647)
### Upcoming Breaking Changes
- k8s (KUBERNETES) Nat method is now deprecated and will be removed in a future release
@ -14,7 +12,8 @@
- Remove privacy test classes support [#7569](https://github.com/hyperledger/besu/pull/7569)
- Add Blob Transaction Metrics [#7622](https://github.com/hyperledger/besu/pull/7622)
- Implemented support for emptyBlockPeriodSeconds in QBFT [#6965](https://github.com/hyperledger/besu/pull/6965)
- Add configuration of Consolidation Request Contract Address via genesis configuration [#7647](https://github.com/hyperledger/besu/pull/7647)
- Interrupt pending transaction processing on block creation timeout [#7673](https://github.com/hyperledger/besu/pull/7673)
### Bug fixes
- Fix mounted data path directory permissions for besu user [#7575](https://github.com/hyperledger/besu/pull/7575)

@ -82,7 +82,7 @@ The following steps must occur for a contributor to be "upgraded" as a maintaine
- The proposed maintainer accepts the nomination and expresses a willingness
to be a long-term (more than 6 month) committer by adding a comment in the proposal PR.
- The PR will be communicated in all appropriate communication channels
including at least [besu-contributors channel on Hyperledger Discord](https://discord.gg/hyperledger),
including at least [besu-contributors channel on Discord](https://discord.gg/hyperledger),
the [mailing list](https://lists.hyperledger.org/g/besu)
and any maintainer/community call.
- Approval by at least 3 current maintainers within two weeks of the proposal or

@ -1,4 +1,4 @@
# Hyperledger Besu Support
# Besu Support
Welcome to the Besu repository! The following links are a set of guidelines for contributing to this repo and its packages. These are mostly guidelines, not rules. Use your best judgement, and feel free to propose changes to this document in a pull request. Contributions come in the form of code submissions, writing documentation, raising issues, helping others in chat, and any other actions that help develop Besu.
@ -6,13 +6,13 @@ Welcome to the Besu repository! The following links are a set of guidelines for
Having Github, Discord, and Linux Foundation accounts is necessary for obtaining support for Besu through the community channels, wiki and issue management.
* If you want to raise an issue, you can do so [on the github issue tab](https://github.com/hyperledger/besu/issues).
* Hyperledger Discord requires a [Discord account].
* The Hyperledger wiki also requires a [Linux Foundation (LF) account] in order to edit pages.
* Discord requires a [Discord account].
* The Besu wiki also requires a [Linux Foundation (LF) account] in order to edit pages.
### Useful support links
* [Besu User Documentation]
* [Besu channel on Hyperledger Discord]
* [Besu channel on Discord]
* [I just have a quick question](https://wiki.hyperledger.org/display/BESU/I+just+have+a+quick+question)
* [Did you find a bug?](https://wiki.hyperledger.org/display/BESU/Reporting+Bugs)
* [Issues](https://wiki.hyperledger.org/display/BESU/Issues)
@ -20,5 +20,5 @@ Having Github, Discord, and Linux Foundation accounts is necessary for obtaining
[Besu User Documentation]: https://besu.hyperledger.org
[Besu channel on Hyperledger Discord]: https://discord.gg/hyperledger
[Besu channel on Discord]: https://discord.gg/hyperledger
[Contributing Guidelines]: CONTRIBUTING.md

@ -282,8 +282,10 @@ public class OperatorSubCommandTest extends CommandTestAbstract {
false,
singletonList("key.pub"),
Optional.empty(),
Optional.of(
"0xf853a00000000000000000000000000000000000000000000000000000000000000000ea94d5feb0fc5a54a89f97aeb34c3df15397c19f6dd294d6a9a4c886eb008ac307abdc1f38745c1dd13a88808400000000c0"));
List.of(
new Field(
"extraData",
"0xf853a00000000000000000000000000000000000000000000000000000000000000000ea94d5feb0fc5a54a89f97aeb34c3df15397c19f6dd294d6a9a4c886eb008ac307abdc1f38745c1dd13a88808400000000c0")));
}
@Test
@ -296,8 +298,49 @@ public class OperatorSubCommandTest extends CommandTestAbstract {
false,
singletonList("key.pub"),
Optional.empty(),
Optional.of(
"0xf84fa00000000000000000000000000000000000000000000000000000000000000000ea94d5feb0fc5a54a89f97aeb34c3df15397c19f6dd294d6a9a4c886eb008ac307abdc1f38745c1dd13a88c080c0"));
List.of(
new Field(
"extraData",
"0xf84fa00000000000000000000000000000000000000000000000000000000000000000ea94d5feb0fc5a54a89f97aeb34c3df15397c19f6dd294d6a9a4c886eb008ac307abdc1f38745c1dd13a88c080c0")));
}
@Test
public void generatedGenesisFileShouldContainAllOriginalFieldsExcludingExtraData()
throws IOException {
final JsonObject alloc =
new JsonObject(
"""
{
"24defc2d149861d3d245749b81fe0e6b28e04f31": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"
},
"2a813d7db3de19b07f92268b6d4125ed295cbe00": {
"balance": "0x446c3b15f9926687d2c40534fdb542000000000000"
}
}""");
final List<Field> fields =
List.of(
new Field("nonce", "0x0"),
new Field("timestamp", "0x5b3c3d18"),
new Field("gasUsed", "0x0"),
new Field(
"parentHash", "0x0000000000000000000000000000000000000000000000000000000000000000"),
new Field("gasLimit", "0x47b760"),
new Field("difficulty", "0x1"),
new Field(
"mixHash", "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365"),
new Field("coinbase", "0x0000000000000000000000000000000000000000"),
new Field("alloc", alloc.getMap().toString()));
runCmdAndCheckOutput(
cmd(),
"/operator/config_generate_keys.json",
tmpOutputDirectoryPath,
"genesis.json",
false,
singletonList("key.pub"),
Optional.empty(),
fields);
}
private void runCmdAndCheckOutput(
@ -316,7 +359,7 @@ public class OperatorSubCommandTest extends CommandTestAbstract {
generate,
expectedKeyFiles,
Optional.empty(),
Optional.empty());
List.of());
}
private void runCmdAndCheckOutput(
@ -336,9 +379,11 @@ public class OperatorSubCommandTest extends CommandTestAbstract {
generate,
expectedKeyFiles,
signatureAlgorithm,
Optional.empty());
List.of());
}
private record Field(String key, String value) {}
private void runCmdAndCheckOutput(
final Cmd cmd,
final String configFile,
@ -347,7 +392,7 @@ public class OperatorSubCommandTest extends CommandTestAbstract {
final boolean generate,
final Collection<String> expectedKeyFiles,
final Optional<SignatureAlgorithm> signatureAlgorithm,
final Optional<String> expectedExtraData)
final List<Field> expectedFields)
throws IOException {
final URL configFilePath = this.getClass().getResource(configFile);
parseCommand(
@ -368,8 +413,9 @@ public class OperatorSubCommandTest extends CommandTestAbstract {
final String genesisString = contentOf(outputGenesisFile, UTF_8);
final JsonObject genesisContent = new JsonObject(genesisString);
assertThat(genesisContent.containsKey("extraData")).isTrue();
expectedExtraData.ifPresent(
extraData -> assertThat(genesisContent.getString("extraData")).isEqualTo(extraData));
expectedFields.forEach(
field -> assertThat(genesisContent.getString(field.key)).isEqualTo(field.value));
final Path expectedKeysPath = outputDirectoryPath.resolve("keys");
final File keysDirectory = new File(expectedKeysPath.toUri());

@ -4,7 +4,9 @@
"chainId": 2017,
"eip150Block": 0,
"ibft2": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
}
},
"nonce": "0x0",
@ -16,11 +18,6 @@
"difficulty": "0x1",
"mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
"coinbase": "0x0000000000000000000000000000000000000000",
"ibft2": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
},
"alloc": {
"24defc2d149861d3d245749b81fe0e6b28e04f31": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"

@ -5,7 +5,9 @@
"eip150Block": 0,
"ecCurve": "abcd",
"ibft2": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
}
},
"nonce": "0x0",
@ -17,11 +19,6 @@
"difficulty": "0x1",
"mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
"coinbase": "0x0000000000000000000000000000000000000000",
"ibft2": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
},
"alloc": {
"24defc2d149861d3d245749b81fe0e6b28e04f31": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"

@ -5,7 +5,9 @@
"eip150Block": 0,
"ecCurve": "secp256r1",
"ibft2": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
}
},
"nonce": "0x0",
@ -17,11 +19,6 @@
"difficulty": "0x1",
"mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
"coinbase": "0x0000000000000000000000000000000000000000",
"ibft2": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
},
"alloc": {
"24defc2d149861d3d245749b81fe0e6b28e04f31": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"

@ -4,7 +4,9 @@
"chainId": 2017,
"petersburgBlock": 0,
"ibft2": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
}
},
"nonce": "0x0",
@ -16,11 +18,6 @@
"difficulty": "0x1",
"mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
"coinbase": "0x0000000000000000000000000000000000000000",
"ibft2": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
},
"alloc": {
"24defc2d149861d3d245749b81fe0e6b28e04f31": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"

@ -4,7 +4,9 @@
"chainId": 2017,
"petersburgBlock": 0,
"ibft2": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
}
},
"nonce": "0x0",
@ -16,11 +18,6 @@
"difficulty": "0x1",
"mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
"coinbase": "0x0000000000000000000000000000000000000000",
"ibft2": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
},
"alloc": {
"24defc2d149861d3d245749b81fe0e6b28e04f31": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"

@ -4,7 +4,9 @@
"chainId": 2017,
"petersburgBlock": 0,
"qbft": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
}
},
"nonce": "0x0",
@ -16,11 +18,6 @@
"difficulty": "0x1",
"mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
"coinbase": "0x0000000000000000000000000000000000000000",
"ibft2": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
},
"alloc": {
"24defc2d149861d3d245749b81fe0e6b28e04f31": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"

@ -5,7 +5,9 @@
"petersburgBlock": 0,
"ecCurve": "secp256r1",
"ibft2": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
}
},
"nonce": "0x0",
@ -17,11 +19,6 @@
"difficulty": "0x1",
"mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
"coinbase": "0x0000000000000000000000000000000000000000",
"ibft2": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
},
"alloc": {
"24defc2d149861d3d245749b81fe0e6b28e04f31": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"

@ -5,7 +5,9 @@
"petersburgBlock": 0,
"ecCurve": "secp256r1",
"ibft2": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
}
},
"nonce": "0x0",
@ -17,11 +19,6 @@
"difficulty": "0x1",
"mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
"coinbase": "0x0000000000000000000000000000000000000000",
"ibft2": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
},
"alloc": {
"24defc2d149861d3d245749b81fe0e6b28e04f31": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"

@ -9,11 +9,6 @@
"difficulty": "0x1",
"mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
"coinbase": "0x0000000000000000000000000000000000000000",
"ibft2": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
},
"alloc": {
"24defc2d149861d3d245749b81fe0e6b28e04f31": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"

@ -53,12 +53,12 @@ interface GenesisReader {
private final ObjectNode rootWithoutAllocations;
public FromObjectNode(final ObjectNode root) {
final var removedAllocations = root.remove(ALLOCATION_FIELD);
this.allocations =
removedAllocations != null
? (ObjectNode) removedAllocations
root.get(ALLOCATION_FIELD) != null
? (ObjectNode) root.get(ALLOCATION_FIELD)
: JsonUtil.createEmptyObjectNode();
this.rootWithoutAllocations = normalizeKeys(root);
this.rootWithoutAllocations =
normalizeKeys(root, field -> !field.getKey().equals(ALLOCATION_FIELD));
}
@Override

@ -25,6 +25,7 @@ import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
@ -37,6 +38,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Predicates;
import org.apache.tuweni.bytes.Bytes;
/** The Json util class. */
@ -59,11 +61,29 @@ public class JsonUtil {
* @return a copy of the json object with all keys in lower case.
*/
public static ObjectNode normalizeKeys(final ObjectNode objectNode) {
return normalizeKeys(objectNode, Predicates.alwaysTrue());
}
/**
* Converts all the object keys (but none of the string values) to lowercase for easier lookup.
* This is useful in cases such as the 'genesis.json' file where all keys are assumed to be case
* insensitive.
*
* @param objectNode The ObjectNode to be normalized
* @param fieldPredicate The predicate to filter the fields to normalize
* @return a copy of the json object with all keys in lower case.
*/
public static ObjectNode normalizeKeys(
final ObjectNode objectNode, final Predicate<Map.Entry<String, JsonNode>> fieldPredicate) {
final ObjectNode normalized = JsonUtil.createEmptyObjectNode();
objectNode
.fields()
.forEachRemaining(
entry -> {
if (!fieldPredicate.test(entry)) {
return;
}
final String key = entry.getKey();
final JsonNode value = entry.getValue();
final String normalizedKey = normalizeKey(key);

@ -53,6 +53,22 @@ public class GenesisReaderTest {
.containsExactly(new GenesisAccount(Address.BLS12_G2MUL, 0, Wei.ONE, null, Map.of(), null));
}
@Test
public void readGenesisFromObjectDoesNotModifyObjectNodeArg() {
final var configNode = mapper.createObjectNode();
configNode.put("londonBlock", 1);
final var allocNode = mapper.createObjectNode();
allocNode.put(Address.BLS12_G2MUL.toUnprefixedHexString(), generateAllocation(Wei.ONE));
final var rootNode = mapper.createObjectNode();
rootNode.put("chainId", 12);
rootNode.put(CONFIG_FIELD, configNode);
rootNode.put(ALLOCATION_FIELD, allocNode);
var rootNodeCopy = rootNode.deepCopy();
new GenesisReader.FromObjectNode(rootNode);
assertThat(rootNode).isEqualTo(rootNodeCopy);
}
@Test
public void readGenesisFromURL(@TempDir final Path folder) throws IOException {
final String jsonStr =

@ -147,6 +147,36 @@ public class JsonUtilTest {
assertThat(normalizedObj).isEqualTo(expectedObj);
}
@Test
public void normalizeKeys_predicate() {
final ObjectNode originalObj =
mapper
.createObjectNode()
.put("Ant", "Tiny")
.put("Ape", "Smart")
.put("Armadillo", "Armored")
.put("Cat", "Meow")
.put("Bat", "Flying")
.put("Cow", "Moo")
.put("Crocodile", "Snap")
.put("Bear", "Strong")
.put("Cheetah", "Fast")
.put("Beaver", "Builder");
final ObjectNode expectedObj =
mapper
.createObjectNode()
.put("cat", "Meow")
.put("cow", "Moo")
.put("cheetah", "Fast")
.put("crocodile", "Snap");
final ObjectNode normalizedObj =
JsonUtil.normalizeKeys(originalObj, s -> s.getKey().startsWith("C"));
assertThat(normalizedObj).isEqualTo(expectedObj);
}
@Test
public void getLong_nonExistentKey() {
final ObjectNode node = mapper.createObjectNode();

@ -49,9 +49,11 @@ public class EngineGetClientVersionV1 extends ExecutionEngineJsonRpcMethod {
@Override
public JsonRpcResponse syncResponse(final JsonRpcRequestContext request) {
String safeCommit =
(commit != null && commit.length() >= 8) ? commit.substring(0, 8) : "unknown";
return new JsonRpcSuccessResponse(
request.getRequest().getId(),
new EngineGetClientVersionResultV1(
ENGINE_CLIENT_CODE, ENGINE_CLIENT_NAME, clientVersion, commit.substring(0, 8)));
ENGINE_CLIENT_CODE, ENGINE_CLIENT_NAME, clientVersion, safeCommit));
}
}

@ -15,6 +15,7 @@
package org.hyperledger.besu.ethereum.blockcreation.txselection;
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.BLOCK_SELECTION_TIMEOUT;
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.BLOCK_SELECTION_TIMEOUT_INVALID_TX;
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.INVALID_TX_EVALUATION_TOO_LONG;
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.SELECTED;
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.TX_EVALUATION_TOO_LONG;
@ -52,9 +53,11 @@ import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
import org.hyperledger.besu.plugin.services.tracer.BlockAwareOperationTracer;
import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelector;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
@ -97,11 +100,12 @@ public class BlockTransactionSelector {
new TransactionSelectionResults();
private final List<AbstractTransactionSelector> transactionSelectors;
private final PluginTransactionSelector pluginTransactionSelector;
private final BlockAwareOperationTracer pluginOperationTracer;
private final BlockAwareOperationTracer operationTracer;
private final EthScheduler ethScheduler;
private final AtomicBoolean isTimeout = new AtomicBoolean(false);
private final long blockTxsSelectionMaxTime;
private WorldUpdater blockWorldStateUpdater;
private volatile TransactionEvaluationContext currTxEvaluationContext;
public BlockTransactionSelector(
final MiningParameters miningParameters,
@ -139,7 +143,8 @@ public class BlockTransactionSelector {
transactionPool);
transactionSelectors = createTransactionSelectors(blockSelectionContext);
this.pluginTransactionSelector = pluginTransactionSelector;
this.pluginOperationTracer = pluginTransactionSelector.getOperationTracer();
this.operationTracer =
new InterruptibleOperationTracer(pluginTransactionSelector.getOperationTracer());
blockWorldStateUpdater = worldState.updater();
blockTxsSelectionMaxTime = miningParameters.getBlockTxsSelectionMaxTime();
}
@ -178,15 +183,17 @@ public class BlockTransactionSelector {
}
private void timeLimitedSelection() {
final var txSelection =
ethScheduler.scheduleBlockCreationTask(
final var txSelectionTask =
new FutureTask<Void>(
() ->
blockSelectionContext
.transactionPool()
.selectTransactions(this::evaluateTransaction));
.selectTransactions(this::evaluateTransaction),
null);
ethScheduler.scheduleBlockCreationTask(txSelectionTask);
try {
txSelection.get(blockTxsSelectionMaxTime, TimeUnit.MILLISECONDS);
txSelectionTask.get(blockTxsSelectionMaxTime, TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException e) {
if (isCancelled.get()) {
throw new CancellationException("Cancelled during transaction selection");
@ -197,6 +204,9 @@ public class BlockTransactionSelector {
synchronized (isTimeout) {
isTimeout.set(true);
}
cancelEvaluatingTxWithGraceTime(txSelectionTask);
LOG.warn(
"Interrupting the selection of transactions for block inclusion as it exceeds the maximum configured duration of "
+ blockTxsSelectionMaxTime
@ -205,6 +215,40 @@ public class BlockTransactionSelector {
}
}
private void cancelEvaluatingTxWithGraceTime(final FutureTask<Void> txSelectionTask) {
final long elapsedTime =
currTxEvaluationContext.getEvaluationTimer().elapsed(TimeUnit.MILLISECONDS);
// adding 100ms so we are sure it take strictly more than the block selection max time
final long txRemainingTime = (blockTxsSelectionMaxTime - elapsedTime) + 100;
LOG.atDebug()
.setMessage(
"Transaction {} is processing for {}ms, giving it {}ms grace time, before considering it taking too much time to execute")
.addArgument(currTxEvaluationContext.getPendingTransaction()::toTraceLog)
.addArgument(elapsedTime)
.addArgument(txRemainingTime)
.log();
ethScheduler.scheduleFutureTask(
() -> {
if (!txSelectionTask.isDone()) {
LOG.atDebug()
.setMessage(
"Transaction {} is still processing after the grace time, total processing time {}ms,"
+ " greater than max block selection time of {}ms, forcing an interrupt")
.addArgument(currTxEvaluationContext.getPendingTransaction()::toTraceLog)
.addArgument(
() ->
currTxEvaluationContext.getEvaluationTimer().elapsed(TimeUnit.MILLISECONDS))
.addArgument(blockTxsSelectionMaxTime)
.log();
txSelectionTask.cancel(true);
}
},
Duration.ofMillis(txRemainingTime));
}
/**
* Evaluates a list of transactions and updates the selection results accordingly. If a
* transaction is not selected during the evaluation, it is updated as not selected in the
@ -236,6 +280,7 @@ public class BlockTransactionSelector {
final TransactionEvaluationContext evaluationContext =
createTransactionEvaluationContext(pendingTransaction);
currTxEvaluationContext = evaluationContext;
TransactionSelectionResult selectionResult = evaluatePreProcessing(evaluationContext);
if (!selectionResult.selected()) {
@ -337,7 +382,7 @@ public class BlockTransactionSelector {
blockSelectionContext.pendingBlockHeader(),
pendingTransaction.getTransaction(),
blockSelectionContext.miningBeneficiary(),
pluginOperationTracer,
operationTracer,
blockHashLookup,
false,
TransactionValidationParams.mining(),
@ -422,14 +467,10 @@ public class BlockTransactionSelector {
final var pendingTransaction = evaluationContext.getPendingTransaction();
// check if this tx took too much to evaluate, and in case it was invalid remove it from the
// pool, otherwise penalize it.
// pool, otherwise penalize it. Not synchronized since there is no state change here.
final TransactionSelectionResult actualResult =
isTimeout.get()
? transactionTookTooLong(evaluationContext, selectionResult)
? selectionResult.discard()
? INVALID_TX_EVALUATION_TOO_LONG
: TX_EVALUATION_TOO_LONG
: BLOCK_SELECTION_TIMEOUT
? rewriteSelectionResultForTimeout(evaluationContext, selectionResult)
: selectionResult;
transactionSelectionResults.updateNotSelected(evaluationContext.getTransaction(), actualResult);
@ -446,6 +487,34 @@ public class BlockTransactionSelector {
return actualResult;
}
/**
* In case of a block creation timeout, we rewrite the selection result, so we can easily spot
* what happened looking at the transaction selection results.
*
* @param evaluationContext The current selection session data.
* @param selectionResult The result of the transaction selection process.
* @return the rewritten selection result
*/
private TransactionSelectionResult rewriteSelectionResultForTimeout(
final TransactionEvaluationContext evaluationContext,
final TransactionSelectionResult selectionResult) {
if (transactionTookTooLong(evaluationContext, selectionResult)) {
return selectionResult.discard() ? INVALID_TX_EVALUATION_TOO_LONG : TX_EVALUATION_TOO_LONG;
}
return selectionResult.discard() ? BLOCK_SELECTION_TIMEOUT_INVALID_TX : BLOCK_SELECTION_TIMEOUT;
}
/**
* Check if the evaluation of this tx took more than the block creation max time, because if true
* we want to penalize it. We penalize it, instead of directly removing, because it could happen
* that the tx will evaluate in time next time. Invalid txs are always removed.
*
* @param evaluationContext The current selection session data.
* @param selectionResult The result of the transaction selection process.
* @return true if the evaluation of this tx took more than the block creation max time
*/
private boolean transactionTookTooLong(
final TransactionEvaluationContext evaluationContext,
final TransactionSelectionResult selectionResult) {

@ -0,0 +1,143 @@
/*
* Copyright contributors to Hyperledger Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.blockcreation.txselection;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Transaction;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.log.Log;
import org.hyperledger.besu.evm.operation.Operation;
import org.hyperledger.besu.evm.worldstate.WorldView;
import org.hyperledger.besu.plugin.data.BlockBody;
import org.hyperledger.besu.plugin.data.BlockHeader;
import org.hyperledger.besu.plugin.data.ProcessableBlockHeader;
import org.hyperledger.besu.plugin.services.tracer.BlockAwareOperationTracer;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.apache.tuweni.bytes.Bytes;
public class InterruptibleOperationTracer implements BlockAwareOperationTracer {
private final BlockAwareOperationTracer delegate;
public InterruptibleOperationTracer(final BlockAwareOperationTracer delegate) {
this.delegate = delegate;
}
@Override
public void traceStartBlock(final BlockHeader blockHeader, final BlockBody blockBody) {
delegate.traceStartBlock(blockHeader, blockBody);
}
@Override
public void traceEndBlock(final BlockHeader blockHeader, final BlockBody blockBody) {
delegate.traceEndBlock(blockHeader, blockBody);
}
@Override
public void traceStartBlock(final ProcessableBlockHeader processableBlockHeader) {
delegate.traceStartBlock(processableBlockHeader);
}
@Override
public boolean isExtendedTracing() {
return delegate.isExtendedTracing();
}
@Override
public void tracePreExecution(final MessageFrame frame) {
checkInterrupt();
delegate.tracePreExecution(frame);
}
@Override
public void tracePostExecution(
final MessageFrame frame, final Operation.OperationResult operationResult) {
checkInterrupt();
delegate.tracePostExecution(frame, operationResult);
}
@Override
public void tracePrecompileCall(
final MessageFrame frame, final long gasRequirement, final Bytes output) {
checkInterrupt();
delegate.tracePrecompileCall(frame, gasRequirement, output);
}
@Override
public void traceAccountCreationResult(
final MessageFrame frame, final Optional<ExceptionalHaltReason> haltReason) {
checkInterrupt();
delegate.traceAccountCreationResult(frame, haltReason);
}
@Override
public void tracePrepareTransaction(final WorldView worldView, final Transaction transaction) {
delegate.tracePrepareTransaction(worldView, transaction);
}
@Override
public void traceStartTransaction(final WorldView worldView, final Transaction transaction) {
delegate.traceStartTransaction(worldView, transaction);
}
@Override
public void traceBeforeRewardTransaction(
final WorldView worldView, final Transaction tx, final Wei miningReward) {
delegate.traceBeforeRewardTransaction(worldView, tx, miningReward);
}
@Override
public void traceEndTransaction(
final WorldView worldView,
final Transaction tx,
final boolean status,
final Bytes output,
final List<Log> logs,
final long gasUsed,
final Set<Address> selfDestructs,
final long timeNs) {
delegate.traceEndTransaction(
worldView, tx, status, output, logs, gasUsed, selfDestructs, timeNs);
}
@Override
public void traceContextEnter(final MessageFrame frame) {
checkInterrupt();
delegate.traceContextEnter(frame);
}
@Override
public void traceContextReEnter(final MessageFrame frame) {
checkInterrupt();
delegate.traceContextReEnter(frame);
}
@Override
public void traceContextExit(final MessageFrame frame) {
checkInterrupt();
delegate.traceContextExit(frame);
}
private void checkInterrupt() {
if (Thread.interrupted()) {
throw new RuntimeException(new InterruptedException("Transaction execution interrupted"));
}
}
}

@ -112,6 +112,7 @@ public class ProcessingResultTransactionSelector extends AbstractTransactionSele
private boolean isTransientValidationError(final TransactionInvalidReason invalidReason) {
return invalidReason.equals(TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE)
|| invalidReason.equals(TransactionInvalidReason.GAS_PRICE_BELOW_CURRENT_BASE_FEE)
|| invalidReason.equals(TransactionInvalidReason.NONCE_TOO_HIGH);
|| invalidReason.equals(TransactionInvalidReason.NONCE_TOO_HIGH)
|| invalidReason.equals(TransactionInvalidReason.EXECUTION_INTERRUPTED);
}
}

@ -14,13 +14,15 @@
*/
package org.hyperledger.besu.ethereum.blockcreation;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
import static org.awaitility.Awaitility.await;
import static org.hyperledger.besu.ethereum.core.MiningParameters.DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME;
import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.EXECUTION_INTERRUPTED;
import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.NONCE_TOO_LOW;
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.BLOCK_SELECTION_TIMEOUT;
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.INVALID_TX_EVALUATION_TOO_LONG;
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.BLOCK_SELECTION_TIMEOUT_INVALID_TX;
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.PRIORITY_FEE_PER_GAS_BELOW_CURRENT_MIN;
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.SELECTED;
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.TX_EVALUATION_TOO_LONG;
@ -90,6 +92,7 @@ import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage;
import org.hyperledger.besu.util.number.PositiveNumber;
import java.math.BigInteger;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
@ -185,6 +188,14 @@ public abstract class AbstractBlockTransactionSelectorTest {
when(ethContext.getEthPeers().subscribeConnect(any())).thenReturn(1L);
when(ethScheduler.scheduleBlockCreationTask(any(Runnable.class)))
.thenAnswer(invocation -> CompletableFuture.runAsync(invocation.getArgument(0)));
when(ethScheduler.scheduleFutureTask(any(Runnable.class), any(Duration.class)))
.thenAnswer(
invocation -> {
final Duration delay = invocation.getArgument(1);
CompletableFuture.delayedExecutor(delay.toMillis(), MILLISECONDS)
.execute(invocation.getArgument(0));
return null;
});
}
protected abstract GenesisConfigFile getGenesisConfigFile();
@ -982,9 +993,17 @@ public abstract class AbstractBlockTransactionSelectorTest {
.TransactionEvaluationContext
ctx = invocation.getArgument(0);
if (ctx.getTransaction().equals(p)) {
try {
Thread.sleep(t);
} catch (final InterruptedException e) {
return TransactionSelectionResult.invalidTransient(EXECUTION_INTERRUPTED.name());
}
} else {
try {
Thread.sleep(fastProcessingTxTime);
} catch (final InterruptedException e) {
return TransactionSelectionResult.invalidTransient(EXECUTION_INTERRUPTED.name());
}
}
return SELECTED;
};
@ -1081,14 +1100,15 @@ public abstract class AbstractBlockTransactionSelectorTest {
processingTooLate,
postProcessingTooLate,
500,
BLOCK_SELECTION_TIMEOUT,
false,
BLOCK_SELECTION_TIMEOUT_INVALID_TX,
true,
NONCE_TOO_LOW);
}
@ParameterizedTest
@MethodSource("subsetOfPendingTransactionsIncludedWhenTxSelectionMaxTimeIsOver")
public void invalidPendingTransactionsThatTakesTooLongToEvaluateIsDroppedFromThePool(
public void
evaluationOfInvalidPendingTransactionThatTakesTooLongToEvaluateIsInterruptedAndPenalized(
final boolean isPoa,
final boolean preProcessingTooLate,
final boolean processingTooLate,
@ -1100,8 +1120,8 @@ public abstract class AbstractBlockTransactionSelectorTest {
processingTooLate,
postProcessingTooLate,
900,
INVALID_TX_EVALUATION_TOO_LONG,
true,
TX_EVALUATION_TOO_LONG,
false,
NONCE_TOO_LOW);
}
@ -1128,9 +1148,17 @@ public abstract class AbstractBlockTransactionSelectorTest {
.TransactionEvaluationContext
ctx = invocation.getArgument(0);
if (ctx.getTransaction().equals(p)) {
try {
Thread.sleep(t);
} catch (final InterruptedException e) {
return TransactionSelectionResult.invalidTransient(EXECUTION_INTERRUPTED.name());
}
} else {
try {
Thread.sleep(fastProcessingTxTime);
} catch (final InterruptedException e) {
return TransactionSelectionResult.invalidTransient(EXECUTION_INTERRUPTED.name());
}
}
return invalidSelectionResult;
};
@ -1199,7 +1227,7 @@ public abstract class AbstractBlockTransactionSelectorTest {
final TransactionSelectionResults results = selector.buildTransactionListForBlock();
// no tx is selected since all are invalid
// no tx is selected since all are invalid or late
assertThat(results.getSelectedTransactions()).isEmpty();
// all txs are not selected so wait until all are evaluated
@ -1350,7 +1378,12 @@ public abstract class AbstractBlockTransactionSelectorTest {
.thenAnswer(
invocation -> {
if (processingTime > 0) {
try {
Thread.sleep(processingTime);
} catch (final InterruptedException e) {
return TransactionProcessingResult.invalid(
ValidationResult.invalid(EXECUTION_INTERRUPTED));
}
}
return TransactionProcessingResult.successful(
new ArrayList<>(),
@ -1375,7 +1408,12 @@ public abstract class AbstractBlockTransactionSelectorTest {
.thenAnswer(
invocation -> {
if (processingTime > 0) {
try {
Thread.sleep(processingTime);
} catch (final InterruptedException e) {
return TransactionProcessingResult.invalid(
ValidationResult.invalid(EXECUTION_INTERRUPTED));
}
}
return TransactionProcessingResult.invalid(ValidationResult.invalid(invalidReason));
});

@ -599,6 +599,12 @@ public class MainnetTransactionProcessor {
EMPTY_ADDRESS_SET,
0L);
final var cause = re.getCause();
if (cause != null && cause instanceof InterruptedException) {
return TransactionProcessingResult.invalid(
ValidationResult.invalid(TransactionInvalidReason.EXECUTION_INTERRUPTED));
}
LOG.error("Critical Exception Processing Transaction", re);
return TransactionProcessingResult.invalid(
ValidationResult.invalid(

@ -45,6 +45,7 @@ public enum TransactionInvalidReason {
MAX_FEE_PER_GAS_BELOW_CURRENT_BASE_FEE,
TX_FEECAP_EXCEEDED,
INTERNAL_ERROR,
EXECUTION_INTERRUPTED,
TX_POOL_DISABLED,
INVALID_BLOBS,
PLUGIN_TX_POOL_VALIDATOR,

@ -236,7 +236,9 @@ public class TransactionSimulator {
: blockHeaderToProcess.getGasLimit();
if (rpcGasCap > 0) {
gasLimit = rpcGasCap;
LOG.info("Capping gasLimit to " + rpcGasCap);
LOG.trace(
"Gas limit capped at {} for transaction simulation due to provided RPC gas cap.",
rpcGasCap);
}
final Wei value = callParams.getValue() != null ? callParams.getValue() : Wei.ZERO;
final Bytes payload = callParams.getPayload() != null ? callParams.getPayload() : Bytes.EMPTY;

@ -351,9 +351,7 @@ public class LayeredPendingTransactions implements PendingTransactions {
}
});
logDiscardedTransaction(candidatePendingTx, selectionResult);
}
if (selectionResult.penalize()) {
} else if (selectionResult.penalize()) {
ethScheduler.scheduleTxWorkerTask(
() -> {
synchronized (this) {

@ -71,7 +71,7 @@ Calculated : ${currentHash}
tasks.register('checkAPIChanges', FileStateChecker) {
description = "Checks that the API for the Plugin-API project does not change without deliberate thought"
files = sourceSets.main.allJava.files
knownHash = 'V/bdVbzJLjdwch266dHHuxIGwiCRhS4w3jDwHt4TWqg='
knownHash = '5H+3gUzCwZtLByfnk11kf+kAPwykQ+WR+n3xWgyfsyY='
}
check.dependsOn('checkAPIChanges')

@ -63,6 +63,7 @@ public class TransactionSelectionResult {
BLOBS_FULL(false, false, false),
BLOCK_OCCUPANCY_ABOVE_THRESHOLD(true, false, false),
BLOCK_SELECTION_TIMEOUT(true, false, false),
BLOCK_SELECTION_TIMEOUT_INVALID_TX(true, true, true),
TX_EVALUATION_TOO_LONG(true, false, true),
INVALID_TX_EVALUATION_TOO_LONG(true, true, true),
INVALID_TRANSIENT(false, false, true),
@ -121,7 +122,11 @@ public class TransactionSelectionResult {
public static final TransactionSelectionResult BLOCK_SELECTION_TIMEOUT =
new TransactionSelectionResult(BaseStatus.BLOCK_SELECTION_TIMEOUT);
/** Transaction took too much to evaluate, but it was not invalid */
/** There was no more time to add transaction to the block, and the transaction is invalid */
public static final TransactionSelectionResult BLOCK_SELECTION_TIMEOUT_INVALID_TX =
new TransactionSelectionResult(BaseStatus.BLOCK_SELECTION_TIMEOUT_INVALID_TX);
/** Transaction took too much to evaluate, but it was valid */
public static final TransactionSelectionResult TX_EVALUATION_TOO_LONG =
new TransactionSelectionResult(BaseStatus.TX_EVALUATION_TOO_LONG);

Loading…
Cancel
Save