diff --git a/CHANGELOG.md b/CHANGELOG.md index ae61ab9d4b0..bcc4dd8ba06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,17 @@ ### Additions and Improvements - Add a block to the bad blocks if it did not descend from the terminal block [#4080](https://github.com/hyperledger/besu/pull/4080) +- Backward sync exception improvements [#4092](https://github.com/hyperledger/besu/pull/4092) +- Remove block header checks during backward sync, since they will be always performed during block import phase [#4098](https://github.com/hyperledger/besu/pull/4098) +- Optimize the backward sync retry strategy [#4095](https://github.com/hyperledger/besu/pull/4095) ### Bug Fixes - Return the correct latest valid hash in case of bad block when calling engine methods [#4056](https://github.com/hyperledger/besu/pull/4056) - Add a PoS block header rule to check that the current block is more recent than its parent [#4066](https://github.com/hyperledger/besu/pull/4066) - Fixed a trie log layer issue on bonsai during reorg [#4069](https://github.com/hyperledger/besu/pull/4069) - Fix transition protocol schedule to return the pre Merge schedule when reorg pre TTD [#4078](https://github.com/hyperledger/besu/pull/4078) +- Remove hash to sync from the queue only if the sync step succeeds [#4105](https://github.com/hyperledger/besu/pull/4105) +- The build process runs successfully even though the system language is not English [#4102](https://github.com/hyperledger/besu/pull/4102) ## 22.7.0-RC1 diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 5603cfa8e15..d4d41408e8e 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -30,7 +30,6 @@ | Stefan Pingel | pinges | pinges | | Danno Ferrin | shemnon | shemnon | | Simon Dudley | siladu | siladu | -| Taccat Isid | taccatisid | taccatisid | | Usman Saleem | usmansaleem | usmansaleem | @@ -49,6 +48,7 @@ | Rai Sur | RatanRSur | ratanraisur | | Rob Dawson | rojotek | RobDawson | | Sajida Zouarhi | sajz | SajidaZ | +| Taccat Isid | taccatisid | taccatisid | | Tim Beiko | timbeiko | timbeiko | | Vijay Michalik | vmichalik | VijayMichalik | diff --git a/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java b/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java index cae72b00225..a21408a80d1 100644 --- a/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java @@ -419,6 +419,8 @@ public Runner build() { } discoveryConfiguration.setBootnodes(bootstrap); discoveryConfiguration.setDnsDiscoveryURL(ethNetworkConfig.getDnsDiscoveryUrl()); + discoveryConfiguration.setDiscoveryV5Enabled( + networkingConfiguration.getDiscovery().isDiscoveryV5Enabled()); } else { discoveryConfiguration.setActive(false); } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/NetworkingOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/NetworkingOptions.java index 2b634cf36a6..bb0045d612f 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/NetworkingOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/NetworkingOptions.java @@ -30,6 +30,7 @@ public class NetworkingOptions implements CLIOptions { private final String CHECK_MAINTAINED_CONNECTIONS_FREQUENCY_FLAG = "--Xp2p-check-maintained-connections-frequency"; private final String DNS_DISCOVERY_SERVER_OVERRIDE_FLAG = "--Xp2p-dns-discovery-server"; + private final String DISCOVERY_PROTOCOL_V5_ENABLED = "--Xv5-discovery-enabled"; @CommandLine.Option( names = INITIATE_CONNECTIONS_FREQUENCY_FLAG, @@ -58,6 +59,13 @@ public class NetworkingOptions implements CLIOptions { "DNS server host to use for doing DNS Discovery of peers, rather than the machine's configured DNS server") private Optional dnsDiscoveryServerOverride = Optional.empty(); + @CommandLine.Option( + names = DISCOVERY_PROTOCOL_V5_ENABLED, + hidden = true, + defaultValue = "false", + description = "Whether to enable P2P Discovery Protocol v5 (default: ${DEFAULT-VALUE})") + private final Boolean isPeerDiscoveryV5Enabled = false; + private NetworkingOptions() {} public static NetworkingOptions create() { @@ -81,7 +89,7 @@ public NetworkingConfiguration toDomainObject() { config.setCheckMaintainedConnectionsFrequency(checkMaintainedConnectionsFrequencySec); config.setInitiateConnectionsFrequency(initiateConnectionsFrequencySec); config.setDnsDiscoveryServerOverride(dnsDiscoveryServerOverride); - + config.getDiscovery().setDiscoveryV5Enabled(isPeerDiscoveryV5Enabled); return config; } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java index e1cbe33e509..eedb1c3a562 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java @@ -17,6 +17,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.startsWith; import static org.hyperledger.besu.cli.config.NetworkName.CLASSIC; import static org.hyperledger.besu.cli.config.NetworkName.DEV; import static org.hyperledger.besu.cli.config.NetworkName.GOERLI; @@ -41,6 +42,7 @@ import static org.hyperledger.besu.ethereum.p2p.config.DefaultDiscoveryConfiguration.RINKEBY_DISCOVERY_URL; import static org.hyperledger.besu.ethereum.worldstate.DataStorageFormat.BONSAI; import static org.hyperledger.besu.nat.kubernetes.KubernetesNatManager.DEFAULT_BESU_SERVICE_NAME_FILTER; +import static org.junit.Assume.assumeThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNotNull; @@ -4755,6 +4757,10 @@ public void eeaWsApisWithPrivacyDisabledLogsWarning() { @Test public void privEnclaveKeyFileDoesNotExist() { + assumeThat( + "Ignored if system language is not English", + System.getProperty("user.language"), + startsWith("en")); parseCommand("--privacy-enabled=true", "--privacy-public-key-file", "/non/existent/file"); assertThat(commandOutput.toString(UTF_8)).isEmpty(); diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/NetworkingOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/NetworkingOptionsTest.java index 4bb39f7b525..330bbc524a1 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/options/NetworkingOptionsTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/NetworkingOptionsTest.java @@ -101,6 +101,30 @@ public void checkDnsServerOverrideFlag_isNotSet() { assertThat(commandOutput.toString(UTF_8)).isEmpty(); } + @Test + public void checkDiscoveryV5Enabled_isSet() { + final TestBesuCommand cmd = parseCommand("--Xv5-discovery-enabled"); + + final NetworkingOptions options = cmd.getNetworkingOptions(); + final NetworkingConfiguration networkingConfig = options.toDomainObject(); + assertThat(networkingConfig.getDiscovery().isDiscoveryV5Enabled()).isTrue(); + + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void checkDiscoveryV5Enabled_isNotSet() { + final TestBesuCommand cmd = parseCommand(); + + final NetworkingOptions options = cmd.getNetworkingOptions(); + final NetworkingConfiguration networkingConfig = options.toDomainObject(); + assertThat(networkingConfig.getDiscovery().isDiscoveryV5Enabled()).isFalse(); + + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } + @Override NetworkingConfiguration createDefaultDomainObject() { return NetworkingConfiguration.create(); diff --git a/config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java index f1eef229d8c..2a5b316809b 100644 --- a/config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java @@ -90,6 +90,8 @@ default boolean isConsensusMigration() { OptionalLong getParisBlockNumber(); + OptionalLong getEIP1153BlockNumber(); + Optional getBaseFeePerGas(); Optional getTerminalTotalDifficulty(); diff --git a/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java index 1158f33bef7..c7699d275a5 100644 --- a/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java @@ -291,6 +291,11 @@ public OptionalLong getParisBlockNumber() { return Streams.concat(parisBlock.stream(), preMergeAlias.stream()).findFirst(); } + @Override + public OptionalLong getEIP1153BlockNumber() { + return getOptionalLong("eip1153Block"); + } + @Override public Optional getBaseFeePerGas() { return Optional.ofNullable(configOverrides.get("baseFeePerGas")) @@ -448,6 +453,7 @@ public Map asMap() { getArrowGlacierBlockNumber().ifPresent(l -> builder.put("arrowGlacierBlock", l)); getGrayGlacierBlockNumber().ifPresent(l -> builder.put("grayGlacierBlock", l)); getParisBlockNumber().ifPresent(l -> builder.put("parisBlock", l)); + getEIP1153BlockNumber().ifPresent(l -> builder.put("eip1153Block", l)); getTerminalBlockNumber().ifPresent(l -> builder.put("terminalBlockNumber", l)); getTerminalBlockHash().ifPresent(h -> builder.put("terminalBlockHash", h.toHexString())); @@ -567,6 +573,7 @@ public List getForks() { getArrowGlacierBlockNumber(), getGrayGlacierBlockNumber(), getParisBlockNumber(), + getEIP1153BlockNumber(), getTerminalBlockNumber(), getEcip1015BlockNumber(), getDieHardBlockNumber(), diff --git a/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java index 882eac9d64d..f4b9ce0e97d 100644 --- a/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java @@ -44,6 +44,7 @@ public class StubGenesisConfigOptions implements GenesisConfigOptions { private OptionalLong arrowGlacierBlockNumber = OptionalLong.empty(); private OptionalLong grayGlacierBlockNumber = OptionalLong.empty(); private OptionalLong parisBlockNumber = OptionalLong.empty(); + private OptionalLong eip1153BlockNumber = OptionalLong.empty(); private OptionalLong terminalBlockNumber = OptionalLong.empty(); private Optional terminalBlockHash = Optional.empty(); private Optional terminalTotalDifficulty = Optional.empty(); @@ -216,6 +217,11 @@ public OptionalLong getParisBlockNumber() { return parisBlockNumber; } + @Override + public OptionalLong getEIP1153BlockNumber() { + return eip1153BlockNumber; + } + @Override public Optional getBaseFeePerGas() { return baseFeePerGas; @@ -344,6 +350,7 @@ public Map asMap() { getArrowGlacierBlockNumber().ifPresent(l -> builder.put("arrowGlacierBlock", l)); getGrayGlacierBlockNumber().ifPresent(l -> builder.put("grayGlacierBlock", l)); getParisBlockNumber().ifPresent(l -> builder.put("parisBlock", l)); + getEIP1153BlockNumber().ifPresent(l -> builder.put("eip1153Block", l)); getTerminalBlockNumber().ifPresent(l -> builder.put("terminalBlockNumber", l)); getTerminalBlockHash().ifPresent(h -> builder.put("terminalBlockHash", h)); // classic fork blocks @@ -482,6 +489,11 @@ public StubGenesisConfigOptions parisBlock(final long blockNumber) { return this; } + public StubGenesisConfigOptions eip1153Block(final long blockNumber) { + eip1153BlockNumber = OptionalLong.of(blockNumber); + return this; + } + public StubGenesisConfigOptions terminalTotalDifficulty( final UInt256 updatedTerminalTotalDifficulty) { terminalTotalDifficulty = Optional.of(updatedTerminalTotalDifficulty); diff --git a/config/src/main/resources/goerli.json b/config/src/main/resources/goerli.json index f819d4deb11..12e52390c24 100644 --- a/config/src/main/resources/goerli.json +++ b/config/src/main/resources/goerli.json @@ -22,9 +22,9 @@ ] }, "checkpoint": { - "hash": "0x2ae30061bdfc7f6dad5b07361dce436502eb0fde68645de12bae4929be619188", - "number": 6720000, - "totalDifficulty": "0x967F81", + "hash": "0x50e55c39a725f062af438c5332a5c5bec9a36d02c829ee6ac2cc27d1db719446", + "number": 4350000, + "totalDifficulty": "0x61DBBF", "_comment": "must be the beginning of an epoch" } }, diff --git a/config/src/main/resources/mainnet.json b/config/src/main/resources/mainnet.json index 1ae9964a92a..805a668c762 100644 --- a/config/src/main/resources/mainnet.json +++ b/config/src/main/resources/mainnet.json @@ -35,9 +35,9 @@ ] }, "checkpoint": { - "hash": "0x844d581cb00058d19f0584fb582fa2de208876ee56bbae27446a679baf4633f4", - "number": 14700000, - "totalDifficulty": "0xA2539264C62BF98CFC6" + "hash": "0x44bca881b07a6a09f83b130798072441705d9a665c5ac8bdf2f39a3cdf3bee29", + "number": 11052984, + "totalDifficulty": "0x3D103014E5C74E5E196" } }, "nonce": "0x42", diff --git a/config/src/main/resources/ropsten.json b/config/src/main/resources/ropsten.json index 3912705c18e..ffd10f68fd3 100644 --- a/config/src/main/resources/ropsten.json +++ b/config/src/main/resources/ropsten.json @@ -23,9 +23,9 @@ ] }, "checkpoint": { - "hash": "0x43de216f876d897e59b9757dd24186e5b53be28bc425ca6a966335b48daaa50c", - "number": 12200000, - "totalDifficulty": "0x928D05243C1CF4" + "hash": "0xeefb1f70bf7bed6394ed7d6f812f422aa37bf7680e1b75fa551d40e849f10a87", + "number": 12269949, + "totalDifficulty": "0x94730AAE0106DC" } }, "nonce": "0x0000000000000042", diff --git a/config/src/main/resources/sepolia.json b/config/src/main/resources/sepolia.json index e91896ef740..d560a5e415e 100644 --- a/config/src/main/resources/sepolia.json +++ b/config/src/main/resources/sepolia.json @@ -19,6 +19,11 @@ "enode://9246d00bc8fd1742e5ad2428b80fc4dc45d786283e05ef6edbd9002cbc335d40998444732fbe921cb88e1d2c73d1b1de53bae6a2237996e9bfe14f871baf7066@18.168.182.86:30303", "enode://ec66ddcf1a974950bd4c782789a7e04f8aa7110a72569b6e65fcd51e937e74eed303b1ea734e4d19cfaec9fbff9b6ee65bf31dcb50ba79acce9dd63a6aca61c7@52.14.151.177:30303" ] + }, + "checkpoint": { + "hash": "0x491ebac1b7f9c0eb426047a495dc577140cb3e09036cd3f7266eda86b635d9fa", + "number": 1273020, + "totalDifficulty": "0x13DE1653E7D280" } }, "alloc":{ diff --git a/config/src/test/resources/all_forks.json b/config/src/test/resources/all_forks.json index 567d6aeac0d..09f49bfdf3f 100644 --- a/config/src/test/resources/all_forks.json +++ b/config/src/test/resources/all_forks.json @@ -14,6 +14,7 @@ "arrowGlacierBlock": 12, "grayGlacierBlock": 13, "parisBlock": 14, + "eip1153Block": 15, "ecip1015Block": 102, "dieHardBlock": 103, "gothamBlock": 104, diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/PostMergeContext.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/PostMergeContext.java index ab9b1cde600..c3013847852 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/PostMergeContext.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/PostMergeContext.java @@ -84,7 +84,7 @@ public PostMergeContext setTerminalTotalDifficulty(final Difficulty newTerminalT @Override public void setIsPostMerge(final Difficulty totalDifficulty) { - if (isPostMerge.get().orElse(Boolean.FALSE) && lastFinalized.get() != null) { + if (isPostMerge.get().orElse(Boolean.FALSE)) { // if we have finalized, we never switch back to a pre-merge once we have transitioned // post-TTD. return; diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinator.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinator.java index e079b662602..cd4643a0a3d 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinator.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinator.java @@ -217,7 +217,9 @@ public Optional getOrSyncHeaderByHash(final Hash blockHash) { debugLambda(LOG, "BlockHeader {} is already present", () -> optHeader.get().toLogString()); } else { debugLambda(LOG, "appending block hash {} to backward sync", blockHash::toHexString); - backwardSyncContext.syncBackwardsUntil(blockHash); + backwardSyncContext + .syncBackwardsUntil(blockHash) + .exceptionally(e -> logSyncException(blockHash, e)); } return optHeader; } @@ -233,11 +235,18 @@ public Optional getOrSyncHeaderByHash( } else { debugLambda(LOG, "appending block hash {} to backward sync", blockHash::toHexString); backwardSyncContext.updateHeads(blockHash, finalizedBlockHash); - backwardSyncContext.syncBackwardsUntil(blockHash); + backwardSyncContext + .syncBackwardsUntil(blockHash) + .exceptionally(e -> logSyncException(blockHash, e)); } return optHeader; } + private Void logSyncException(final Hash blockHash, final Throwable exception) { + LOG.warn("Sync to block hash " + blockHash.toHexString() + " failed", exception); + return null; + } + @Override public Result validateBlock(final Block block) { 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 58ff9d35839..e5923cf5d18 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 @@ -23,7 +23,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -50,6 +49,7 @@ import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import java.util.Optional; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicLong; import org.apache.tuweni.bytes.Bytes32; @@ -280,10 +280,12 @@ public void assertGetOrSyncForBlockAlreadyPresent() { public void assertGetOrSyncForBlockNotPresent() { BlockHeader mockHeader = headerGenerator.parentHash(Hash.fromHexStringLenient("0xbeef")).buildHeader(); + when(backwardSyncContext.syncBackwardsUntil(mockHeader.getBlockHash())) + .thenReturn(CompletableFuture.completedFuture(null)); + var res = coordinator.getOrSyncHeaderByHash(mockHeader.getHash()); assertThat(res).isNotPresent(); - verify(backwardSyncContext, times(1)).syncBackwardsUntil(mockHeader.getHash()); } @Test diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/ExecutionEngineJsonRpcMethod.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/ExecutionEngineJsonRpcMethod.java index 85022820cdf..58a845283b1 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/ExecutionEngineJsonRpcMethod.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/ExecutionEngineJsonRpcMethod.java @@ -66,7 +66,7 @@ public final JsonRpcResponse response(final JsonRpcRequestContext request) { cf.complete( resp.otherwise( t -> { - LOG.debug( + LOG.error( String.format("failed to exec consensus method %s", this.getName()), t); return new JsonRpcErrorResponse( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java index 59bff87e2d0..dc1002a769e 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java @@ -165,7 +165,13 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) new Block(newBlockHeader, new BlockBody(transactions, Collections.emptyList())); if (mergeContext.isSyncing() || parentHeader.isEmpty()) { - mergeCoordinator.appendNewPayloadToSync(block); + mergeCoordinator + .appendNewPayloadToSync(block) + .exceptionally( + exception -> { + LOG.warn("Sync to block " + block.toLogString() + " failed", exception); + return null; + }); return respondWith(reqId, blockParam, null, SYNCING); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/blockheaders/NewBlockHeadersSubscriptionService.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/blockheaders/NewBlockHeadersSubscriptionService.java index d97bae0bd7c..780caef5043 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/blockheaders/NewBlockHeadersSubscriptionService.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/blockheaders/NewBlockHeadersSubscriptionService.java @@ -22,7 +22,7 @@ import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.chain.BlockAddedEvent; import org.hyperledger.besu.ethereum.chain.BlockAddedObserver; -import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockHeader; import java.util.ArrayList; import java.util.Collections; @@ -46,8 +46,8 @@ public NewBlockHeadersSubscriptionService( @Override public void onBlockAdded(final BlockAddedEvent event) { if (event.isNewCanonicalHead()) { - final List blocks = new ArrayList<>(); - Block blockPtr = event.getBlock(); + final List blocks = new ArrayList<>(); + BlockHeader blockPtr = event.getBlock().getHeader(); while (!blockPtr.getHash().equals(event.getCommonAncestorHash())) { blocks.add(blockPtr); @@ -55,7 +55,7 @@ public void onBlockAdded(final BlockAddedEvent event) { blockPtr = blockchainQueries .getBlockchain() - .getBlockByHash(blockPtr.getHeader().getParentHash()) + .getBlockHeader(blockPtr.getParentHash()) .orElseThrow(() -> new IllegalStateException("The block was on a orphaned chain.")); } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadTest.java index 5e25fef895e..e17cc083854 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadTest.java @@ -52,6 +52,7 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.concurrent.CompletableFuture; import io.vertx.core.Vertx; import org.apache.tuweni.bytes.Bytes32; @@ -263,7 +264,8 @@ public void shouldRespondWithSyncingDuringForwardSync() { BlockHeader mockHeader = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE).buildHeader(); when(blockchain.getBlockByHash(any())).thenReturn(Optional.empty()); when(mergeContext.isSyncing()).thenReturn(Boolean.TRUE); - + when(mergeCoordinator.appendNewPayloadToSync(any())) + .thenReturn(CompletableFuture.completedFuture(null)); var resp = resp(mockPayload(mockHeader, Collections.emptyList())); EnginePayloadStatusResult res = fromSuccessResp(resp); @@ -275,7 +277,8 @@ public void shouldRespondWithSyncingDuringForwardSync() { @Test public void shouldRespondWithSyncingDuringBackwardsSync() { BlockHeader mockHeader = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE).buildHeader(); - + when(mergeCoordinator.appendNewPayloadToSync(any())) + .thenReturn(CompletableFuture.completedFuture(null)); var resp = resp(mockPayload(mockHeader, Collections.emptyList())); EnginePayloadStatusResult res = fromSuccessResp(resp); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiAccount.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiAccount.java index 7cf935c28fd..23bda3f5c56 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiAccount.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiAccount.java @@ -52,6 +52,7 @@ public class BonsaiAccount implements MutableAccount, EvmAccount { private Bytes code; private final Map updatedStorage = new HashMap<>(); + private final Map updatedTransientStorage = new HashMap<>(); BonsaiAccount( final BonsaiWorldView context, @@ -103,6 +104,7 @@ public class BonsaiAccount implements MutableAccount, EvmAccount { this.codeHash = toCopy.codeHash; this.code = toCopy.code; updatedStorage.putAll(toCopy.updatedStorage); + updatedTransientStorage.putAll(toCopy.updatedTransientStorage); this.mutable = mutable; } @@ -117,7 +119,7 @@ public class BonsaiAccount implements MutableAccount, EvmAccount { this.codeHash = tracked.getCodeHash(); this.code = tracked.getCode(); updatedStorage.putAll(tracked.getUpdatedStorage()); - + updatedTransientStorage.putAll(tracked.getUpdatedTransientStorage()); this.mutable = true; } @@ -243,6 +245,7 @@ public void setStorageValue(final UInt256 key, final UInt256 value) { @Override public void clearStorage() { updatedStorage.clear(); + updatedTransientStorage.clear(); } @Override @@ -250,6 +253,11 @@ public Map getUpdatedStorage() { return updatedStorage; } + @Override + public Map getUpdatedTransientStorage() { + return updatedTransientStorage; + } + @Override public MutableAccount getMutable() throws ModificationNotAllowedException { if (mutable) { @@ -310,4 +318,21 @@ static void assertCloseEnoughForDiffing( } } } + + @Override + public UInt256 getTransientStorageValue(final UInt256 key) { + if (updatedTransientStorage.containsKey(key)) { + return updatedTransientStorage.get(key); + } else { + return UInt256.ZERO; + } + } + + @Override + public void setTransientStorageValue(final UInt256 key, final UInt256 value) { + if (!mutable) { + throw new UnsupportedOperationException("Account is immutable"); + } + updatedTransientStorage.put(key, value); + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/debug/TraceFrame.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/debug/TraceFrame.java index 89c9ac7ea6b..36291748de9 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/debug/TraceFrame.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/debug/TraceFrame.java @@ -58,6 +58,7 @@ public class TraceFrame { private final boolean virtualOperation; private final Optional maybeUpdatedMemory; private final Optional maybeUpdatedStorage; + private final Optional maybeUpdatedTransientStorage; private OptionalLong precompiledGasCost; public TraceFrame( @@ -83,7 +84,8 @@ public TraceFrame( final Optional stackPostExecution, final boolean virtualOperation, final Optional maybeUpdatedMemory, - final Optional maybeUpdatedStorage) { + final Optional maybeUpdatedStorage, + final Optional maybeUpdatedTransientStorage) { this.pc = pc; this.opcode = opcode; this.gasRemaining = gasRemaining; @@ -107,6 +109,7 @@ public TraceFrame( this.virtualOperation = virtualOperation; this.maybeUpdatedMemory = maybeUpdatedMemory; this.maybeUpdatedStorage = maybeUpdatedStorage; + this.maybeUpdatedTransientStorage = maybeUpdatedTransientStorage; precompiledGasCost = OptionalLong.empty(); } @@ -229,6 +232,10 @@ public Optional getMaybeUpdatedStorage() { return maybeUpdatedStorage; } + public Optional getMaybeUpdatedTransientStorage() { + return maybeUpdatedTransientStorage; + } + public OptionalLong getPrecompiledGasCost() { return precompiledGasCost; } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecFactory.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecFactory.java index 95b211c09fb..e4f7a4eea95 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecFactory.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecFactory.java @@ -185,6 +185,17 @@ public ProtocolSpecBuilder parisDefinition(final GenesisConfigOptions genesisCon evmConfiguration); } + public ProtocolSpecBuilder eip1153Definition(final GenesisConfigOptions genesisConfigOptions) { + return MainnetProtocolSpecs.eip1153Definition( + chainId, + contractSizeLimit, + evmStackSize, + isRevertReasonEnabled, + genesisConfigOptions, + quorumCompatibilityMode, + evmConfiguration); + } + //////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////// // Classic Protocol Specs diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java index 3d98746c0f4..78724a80c7e 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java @@ -50,6 +50,7 @@ import org.hyperledger.besu.evm.gascalculator.PetersburgGasCalculator; import org.hyperledger.besu.evm.gascalculator.SpuriousDragonGasCalculator; import org.hyperledger.besu.evm.gascalculator.TangerineWhistleGasCalculator; +import org.hyperledger.besu.evm.gascalculator.EIP1153GasCalculator; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.processor.ContractCreationProcessor; import org.hyperledger.besu.evm.processor.MessageCallProcessor; @@ -632,6 +633,31 @@ static ProtocolSpecBuilder parisDefinition( .name("ParisFork"); } + // TODO EIP-1153 change for the actual fork name when known + static ProtocolSpecBuilder eip1153Definition( + final Optional chainId, + final OptionalInt configContractSizeLimit, + final OptionalInt configStackSizeLimit, + final boolean enableRevertReason, + final GenesisConfigOptions genesisConfigOptions, + final boolean quorumCompatibilityMode, + final EvmConfiguration evmConfiguration) { + + return parisDefinition( + chainId, + configContractSizeLimit, + configStackSizeLimit, + enableRevertReason, + genesisConfigOptions, + quorumCompatibilityMode, + evmConfiguration) + .evmBuilder( + (gasCalculator, jdCacheConfig) -> + MainnetEVMs.eip1153(gasCalculator, chainId.orElse(BigInteger.ZERO), evmConfiguration)) + .gasCalculator(EIP1153GasCalculator::new) + .name("EIP-1153"); + } + private static TransactionReceipt frontierTransactionReceiptFactory( // ignored because it's always FRONTIER final TransactionType __, diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilder.java index 3fccd7b6f43..aa3ec35d879 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilder.java @@ -234,7 +234,8 @@ private TreeMap buildMilestoneMap( create( config.getGrayGlacierBlockNumber(), specFactory.grayGlacierDefinition(config)), create(config.getParisBlockNumber(), specFactory.parisDefinition(config)), - // Classic Milestones + create(config.getEIP1153BlockNumber(), specFactory.eip1153Definition(config)), + // Classic Milestones create(config.getEcip1015BlockNumber(), specFactory.tangerineWhistleDefinition()), create(config.getDieHardBlockNumber(), specFactory.dieHardDefinition()), create(config.getGothamBlockNumber(), specFactory.gothamDefinition()), diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java index a97f17f6ab3..534ff936397 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java @@ -93,7 +93,8 @@ public void traceExecution(final MessageFrame frame, final ExecuteOperation exec stackPostExecution, currentOperation.isVirtualOperation(), frame.getMaybeUpdatedMemory(), - frame.getMaybeUpdatedStorage()); + frame.getMaybeUpdatedStorage(), + frame.getMaybeUpdatedTransientStorage()); traceFrames.add(lastFrame); frame.reset(); } @@ -126,6 +127,7 @@ public void tracePrecompileCall( Optional.empty(), true, Optional.empty(), + Optional.empty(), Optional.empty()); traceFrames.add(traceFrame); } @@ -172,6 +174,7 @@ public void traceAccountCreationResult( Optional.empty(), true, Optional.empty(), + Optional.empty(), Optional.empty()); traceFrames.add(traceFrame); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutableWorldState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutableWorldState.java index 87e2a82aa6c..16ec3c6e7f3 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutableWorldState.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutableWorldState.java @@ -311,6 +311,12 @@ public UInt256 getOriginalStorageValue(final UInt256 key) { return getStorageValue(key); } + @Override + public UInt256 getTransientStorageValue(final UInt256 key) { + // Transient state isn't persistent + return UInt256.ZERO; + } + @Override public NavigableMap storageEntriesFrom( final Bytes32 startKeyHash, final int limit) { diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/ByteCodeBuilder.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/ByteCodeBuilder.java new file mode 100644 index 00000000000..d41e271f180 --- /dev/null +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/ByteCodeBuilder.java @@ -0,0 +1,187 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.units.bigints.UInt256; +import org.hyperledger.besu.datatypes.Address; + +public class ByteCodeBuilder { + + public enum Operation { + ADD("01"), + CALL("f1"), + CALLDATALOAD("35"), + CALLER("33"), + DELEGATECALL("f4"), + EQ("14"), + JUMPDEST("5b"), + JUMPI("57"), + MLOAD("51"), + MSTORE("52"), + PUSH("60"), + RETURN("f3"), + RETURNDATACOPY("3e"), + REVERT("fd"), + STATICCALL("fa"), + TLOAD("b3"), + TSTORE("b4"); + + private final String value; + + Operation(final String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + } + StringBuilder byteCode = new StringBuilder("0x"); + + @Override + public String toString() { + return byteCode.toString(); + } + public Bytes toBytes() { + return Bytes.fromHexString(byteCode.toString()); + } + + public ByteCodeBuilder push(final int value) { + return this.push(UInt256.valueOf(value)); + } + public ByteCodeBuilder push(final Bytes value) { + UInt256 pushSize = UInt256.valueOf(Bytes.fromHexString("0x60").toInt() + value.size() - 1); + String byteString = standardizeByteString(pushSize); + byteCode.append(byteString); + byteCode.append(value.toUnprefixedHexString()); + return this; + } + public ByteCodeBuilder push(final UInt256 value) { + String byteString = standardizeByteString(value); + byteCode.append(Operation.PUSH); + byteCode.append(byteString); + return this; + } + + private String standardizeByteString(final UInt256 value) { + String byteString = value.toMinimalBytes().toUnprefixedHexString(); + while (byteString.length() < 2) { + byteString = "0" + byteString; + } + return byteString; + } + + public ByteCodeBuilder tstore(final int key, final int value) { + this.push(value); + this.push(key); + byteCode.append(Operation.TSTORE); + return this; + } + + public ByteCodeBuilder tload(final int key) { + this.push(key); + byteCode.append(Operation.TLOAD); + return this; + } + + public ByteCodeBuilder dataOnStackToMemory(final int key) { + this.push(key); + this.op(Operation.MSTORE); + return this; + } + + public ByteCodeBuilder returnValueAtMemory(final int size, final int key) { + this.push(size); + this.push(key); + this.op(Operation.RETURN); + return this; + } + + public ByteCodeBuilder call(final Operation callType, final Address address, final UInt256 gasLimit) { + callTypeCheck(callType); + this.push(0); + this.push(0); + this.push(0); + this.push(0); + this.push(0); + this.push(Bytes.fromHexString(address.toHexString())); + this.push(gasLimit.toMinimalBytes()); + this.op(callType); + return this; + } + + private static void callTypeCheck(final Operation callType) { + + if (callType != Operation.CALL && + callType != Operation.STATICCALL && + callType != Operation.DELEGATECALL) { + throw new UnsupportedOperationException("callType not supported: " + callType); + } + } + + public ByteCodeBuilder callWithInput(final Operation callType, final Address address, final UInt256 gasLimit) { + return callWithInput(callType, address, gasLimit, null); + } + public ByteCodeBuilder callWithInput(final Operation callType, final Address address, final UInt256 gasLimit, final UInt256 input) { + callTypeCheck(callType); + if (input != null) { + this.push(input); + this.push(0); + this.op(Operation.MSTORE); + } + else { + // Use top of stack as input + dataOnStackToMemory(0); + } + + this.push(0); + this.push(0); + this.push(32); + this.push(0); + this.push(0); + this.push(Bytes.fromHexString(address.toHexString())); + this.push(gasLimit.toMinimalBytes()); + this.op(callType); + return this; + } + + public ByteCodeBuilder returnInnerCallResults() { + this.push(32); + this.push(0); + this.push(0); + byteCode.append(Operation.RETURNDATACOPY); + this.returnValueAtMemory(32, 0); + return this; + } + + public ByteCodeBuilder callerIs(final Address caller) { + byteCode.append(Operation.CALLER); + this.push(Bytes.fromHexString(caller.toHexString())); + byteCode.append(Operation.EQ); + return this; + } + + public ByteCodeBuilder conditionalJump(final int dest) { + this.push(dest); + byteCode.append(Operation.JUMPI); + return this; + } + public ByteCodeBuilder op(final Operation operation) { + byteCode.append(operation.toString()); + return this; + } +} diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TestCodeExecutor.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TestCodeExecutor.java index fe113e7e302..be15ebe630c 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TestCodeExecutor.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TestCodeExecutor.java @@ -44,20 +44,24 @@ public class TestCodeExecutor { private final BlockHeader blockHeader = new BlockHeaderTestFixture().number(13).buildHeader(); private static final Address SENDER_ADDRESS = AddressHelpers.ofValue(244259721); - public TestCodeExecutor(final ProtocolSchedule protocolSchedule) { + private final WorldUpdater worldUpdater; + private final EVM evm; + + public TestCodeExecutor( + final ProtocolSchedule protocolSchedule, + final Consumer accountSetup) { fixture = ExecutionContextTestFixture.builder().protocolSchedule(protocolSchedule).build(); + final ProtocolSpec protocolSpec = fixture.getProtocolSchedule().getByBlockNumber(0); + worldUpdater = + createInitialWorldState(accountSetup, fixture.getStateArchive()); + evm = protocolSpec.getEvm(); } public MessageFrame executeCode( final String codeHexString, - final long gasLimit, - final Consumer accountSetup) { - final ProtocolSpec protocolSpec = fixture.getProtocolSchedule().getByBlockNumber(0); - final WorldUpdater worldUpdater = - createInitialWorldState(accountSetup, fixture.getStateArchive()); + final long gasLimit) { final Deque messageFrameStack = new ArrayDeque<>(); - final EVM evm = protocolSpec.getEvm(); final MessageCallProcessor messageCallProcessor = new MessageCallProcessor(evm, new PrecompileContractRegistry()); final Bytes codeBytes = Bytes.fromHexString(codeHexString); @@ -102,6 +106,19 @@ public MessageFrame executeCode( return initialFrame; } + public void deployContract( + final Address contractAddress, + final String codeHexString + ) { + final MutableAccount contract = + worldUpdater.getOrCreate(contractAddress).getMutable(); + + contract.setNonce(0); + contract.clearStorage(); + contract.setCode(Bytes.fromHexStringLenient(codeHexString)); + worldUpdater.updater().commit(); + } + private WorldUpdater createInitialWorldState( final Consumer accountSetup, final WorldStateArchive stateArchive) { final MutableWorldState initialWorldState = stateArchive.getMutable(); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/operations/ConstantinopleSStoreOperationGasCostTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/operations/ConstantinopleSStoreOperationGasCostTest.java index b90b7d65566..9dab258eec9 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/operations/ConstantinopleSStoreOperationGasCostTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/operations/ConstantinopleSStoreOperationGasCostTest.java @@ -78,7 +78,9 @@ public static Object[][] scenarios() { @Before public void setUp() { - codeExecutor = new TestCodeExecutor(protocolSchedule); + codeExecutor = new TestCodeExecutor( + protocolSchedule, + account -> account.setStorageValue(UInt256.ZERO, UInt256.valueOf(originalValue))); } @Test @@ -87,8 +89,7 @@ public void shouldCalculateGasAccordingToEip1283() { final MessageFrame frame = codeExecutor.executeCode( code, - gasLimit, - account -> account.setStorageValue(UInt256.ZERO, UInt256.valueOf(originalValue))); + gasLimit); assertThat(frame.getState()).isEqualTo(State.COMPLETED_SUCCESS); assertThat(frame.getRemainingGas()).isEqualTo(gasLimit - expectedGasUsed); assertThat(frame.getGasRefund()).isEqualTo(expectedGasRefund); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/operations/LondonSStoreOperationGasCostTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/operations/LondonSStoreOperationGasCostTest.java index 1b162b11dcc..f42b9278464 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/operations/LondonSStoreOperationGasCostTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/operations/LondonSStoreOperationGasCostTest.java @@ -79,7 +79,9 @@ public void setUp() { protocolSchedule = MainnetProtocolSchedule.fromConfig( new StubGenesisConfigOptions().londonBlock(0), EvmConfiguration.DEFAULT); - codeExecutor = new TestCodeExecutor(protocolSchedule); + codeExecutor = new TestCodeExecutor( + protocolSchedule, + account -> account.setStorageValue(UInt256.ZERO, UInt256.valueOf(originalValue))); } @Test @@ -88,8 +90,7 @@ public void shouldCalculateGasAccordingToEip3529() { final MessageFrame frame = codeExecutor.executeCode( code, - gasLimit, - account -> account.setStorageValue(UInt256.ZERO, UInt256.valueOf(originalValue))); + gasLimit); assertThat(frame.getState()).isEqualTo(State.COMPLETED_SUCCESS); assertThat(frame.getRemainingGas()).isEqualTo(gasLimit - (expectedGasUsed + 2100)); assertThat(frame.getGasRefund()).isEqualTo(expectedGasRefund); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/operations/TStoreEVMOperationTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/operations/TStoreEVMOperationTest.java new file mode 100644 index 00000000000..6ec0463b5d3 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/operations/TStoreEVMOperationTest.java @@ -0,0 +1,402 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations; + +import org.apache.tuweni.units.bigints.UInt256; +import org.hyperledger.besu.config.StubGenesisConfigOptions; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.core.AddressHelpers; +import org.hyperledger.besu.ethereum.core.ByteCodeBuilder; +import org.hyperledger.besu.ethereum.core.ByteCodeBuilder.Operation; +import org.hyperledger.besu.ethereum.core.TestCodeExecutor; +import org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.frame.MessageFrame.State; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(Parameterized.class) +public class TStoreEVMOperationTest { + + private static final UInt256 gasLimit = UInt256.valueOf(50_000); + private static final Address contractAddress = AddressHelpers.ofValue(28499200); + + @Parameters(name = "ByteCodeBuilder: {0}, Expected Result State: {1}, Expected Return Value: {2}") + public static Object[][] scenarios() { + // Tests specified in EIP-1153. + return new Object[][] { + // Can tstore + {null, new ByteCodeBuilder().tstore(1, 1), State.COMPLETED_SUCCESS, 0}, + // Can tload uninitialized + {null, + new ByteCodeBuilder() + .tload(1) + .dataOnStackToMemory(0) + .returnValueAtMemory(32, 0), State.COMPLETED_SUCCESS, 0}, + // Can tload after tstore + {null, + new ByteCodeBuilder() + .tstore(1, 2) + .tload(1) + .dataOnStackToMemory(0) + .returnValueAtMemory(32, 0), State.COMPLETED_SUCCESS, 2}, + // Can tload after tstore from different location + {null, + new ByteCodeBuilder() + .tstore(1, 2) + .tload(2) + .dataOnStackToMemory(0) + .returnValueAtMemory(32, 0), State.COMPLETED_SUCCESS, 0}, + // Contracts have separate transient storage + {new ByteCodeBuilder() + .tload(1) + .dataOnStackToMemory(0) + .returnValueAtMemory(32, 0), + new ByteCodeBuilder() + .tstore(1, 2) + .call(Operation.CALL, contractAddress, gasLimit) + .returnInnerCallResults(), + State.COMPLETED_SUCCESS, + 0}, + // Reentrant calls access the same transient storage + {new ByteCodeBuilder() + // check if caller is self + .callerIs(contractAddress) + .conditionalJump(78) + // non-reentrant, call self after tstore + .tstore(1, 8) + .call(Operation.CALL, contractAddress, gasLimit) + .returnInnerCallResults() + // reentrant, TLOAD and return value + .op(Operation.JUMPDEST) + .tload(1) + .dataOnStackToMemory(0) + .returnValueAtMemory(32, 0), + new ByteCodeBuilder() + .call(Operation.CALL, contractAddress, gasLimit) + .returnInnerCallResults(), + State.COMPLETED_SUCCESS, + 8}, + // Successfully returned calls do not revert transient storage writes + {new ByteCodeBuilder() + // check if caller is self + .callerIs(contractAddress) + .conditionalJump(77) + // non-reentrant, call self after tstore + .tstore(1, 8) + .call(Operation.CALL, contractAddress, gasLimit) + .tload(1) + .dataOnStackToMemory(0) + .returnValueAtMemory(32, 0) + // reentrant, TLOAD and return value + .op(Operation.JUMPDEST) + .tstore(1, 9), + new ByteCodeBuilder() + .call(Operation.CALL, contractAddress, gasLimit) + .returnInnerCallResults(), + State.COMPLETED_SUCCESS, + 9}, + // Revert undoes the transient storage write from the failed call + {new ByteCodeBuilder() + // check if caller is self + .callerIs(contractAddress) + .conditionalJump(77) + // non-reentrant, call self after tstore + .tstore(1, 8) + .call(Operation.CALL, contractAddress, gasLimit) + .tload(1) + .dataOnStackToMemory(0) + .returnValueAtMemory(32, 0) + // reentrant, TLOAD and return value + .op(Operation.JUMPDEST) + .tstore(1, 9) + .op(Operation.REVERT), + new ByteCodeBuilder() + .call(Operation.CALL, contractAddress, gasLimit) + .returnInnerCallResults(), + State.COMPLETED_SUCCESS, + 8}, + // Revert undoes all the transient storage writes to the same key from the failed call + {new ByteCodeBuilder() + // check if caller is self + .callerIs(contractAddress) + .conditionalJump(77) + // non-reentrant, call self after tstore + .tstore(1, 8) + .call(Operation.CALL, contractAddress, gasLimit) + .tload(1) + .dataOnStackToMemory(0) + .returnValueAtMemory(32, 0) + // reentrant, TLOAD and return value + .op(Operation.JUMPDEST) + .tstore(1, 9) + .tstore(1, 10) + .op(Operation.REVERT), + new ByteCodeBuilder() + .call(Operation.CALL, contractAddress, gasLimit) + .returnInnerCallResults(), + State.COMPLETED_SUCCESS, + 8}, + // Revert undoes transient storage writes from inner calls that successfully returned + {new ByteCodeBuilder() + // Check call depth + .push(0) + .op(Operation.CALLDATALOAD) + // Store input in mem and reload it to stack + .dataOnStackToMemory(5) + .push(5) + .op(Operation.MLOAD) + + // See if we're at call depth 1 + .push(1) + .op(Operation.EQ) + .conditionalJump(84) + + // See if we're at call depth 2 + .push(5) + .op(Operation.MLOAD) + .push(2) + .op(Operation.EQ) + .conditionalJump(135) + + // Call depth = 0, call self after TSTORE 8 + .tstore(1, 8) + + // Recursive call with input + // Depth++ + .push(5) + .op(Operation.MLOAD) + .push(1) + .op(Operation.ADD) + .callWithInput(Operation.CALL, contractAddress, gasLimit) + + // TLOAD and return value + .tload(1) + .dataOnStackToMemory(0) + .returnValueAtMemory(32, 0) + + // Call depth 1, TSTORE 9 but REVERT after recursion + .op(Operation.JUMPDEST) + .tstore(1, 9) + + // Recursive call with input + // Depth++ + .push(5) + .op(Operation.MLOAD) + .push(1) + .op(Operation.ADD) + .callWithInput(Operation.CALL, contractAddress, gasLimit) + + .op(Operation.REVERT) + + // Call depth 2, TSTORE 10 and complete + .op(Operation.JUMPDEST) + .tstore(1, 10) + , + new ByteCodeBuilder() + // Call with input 0 + .callWithInput(Operation.CALL, contractAddress, gasLimit, UInt256.valueOf(0)) + .returnInnerCallResults(), + State.COMPLETED_SUCCESS, + 8}, + // Transient storage cannot be manipulated in a static context + {new ByteCodeBuilder() + .tstore(1, 8) + .tload(1) + .dataOnStackToMemory(0) + .returnValueAtMemory(32, 0), + new ByteCodeBuilder() + .call(Operation.STATICCALL, contractAddress, gasLimit) + .returnInnerCallResults(), + State.COMPLETED_FAILED, + 0}, + // Transient storage cannot be manipulated in a static context when calling self + {new ByteCodeBuilder() + // Check if caller is self + .callerIs(contractAddress) + .conditionalJump(82) + + // Non-reentrant, call self after TSTORE 8 + .tstore(1, 8) + .callWithInput(Operation.STATICCALL, contractAddress, gasLimit, UInt256.valueOf(0)) + // Return the TLOAD value + // Should be 8 if call fails, 9 if success + .tload(1) + .dataOnStackToMemory(0) + .returnValueAtMemory(32, 0) + + // Reentrant, TSTORE 9 + .op(Operation.JUMPDEST) + .tstore(1, 9), + new ByteCodeBuilder() + .call(Operation.CALL, contractAddress, gasLimit) + .returnInnerCallResults(), + State.COMPLETED_SUCCESS, + 8}, + // Transient storage cannot be manipulated in a nested static context + {new ByteCodeBuilder() + // Check call depth + .push(0) + .op(Operation.CALLDATALOAD) + // Store input in mem and reload it to stack + .dataOnStackToMemory(5) + .push(5) + .op(Operation.MLOAD) + + // See if we're at call depth 1 + .push(1) + .op(Operation.EQ) + .conditionalJump(84) + + // See if we're at call depth 2 + .push(5) + .op(Operation.MLOAD) + .push(2) + .op(Operation.EQ) + .conditionalJump(140) + + // Call depth = 0, call self after TSTORE 8 + .tstore(1, 8) + + // Recursive call with input + // Depth++ + .push(5) + .op(Operation.MLOAD) + .push(1) + .op(Operation.ADD) + .callWithInput(Operation.STATICCALL, contractAddress, gasLimit) + + // TLOAD and return value + .tload(1) + .dataOnStackToMemory(0) + .returnValueAtMemory(32, 0) + + // Call depth 1, TSTORE 9 but REVERT after recursion + .op(Operation.JUMPDEST) // 84 + + // Recursive call with input + // Depth++ + .push(5) + .op(Operation.MLOAD) + .push(1) + .op(Operation.ADD) + .callWithInput(Operation.CALL, contractAddress, gasLimit) + + // TLOAD and return value + .tload(1) + .dataOnStackToMemory(0) + .returnValueAtMemory(32, 0) + + // Call depth 2, TSTORE 10 and complete + .op(Operation.JUMPDEST) // 140 + .tstore(1, 10) // this call will fail + , + new ByteCodeBuilder() + // Call with input 0 + .callWithInput(Operation.CALL, contractAddress, gasLimit, UInt256.valueOf(0)) + .returnInnerCallResults(), + State.COMPLETED_SUCCESS, + 8}, + // Delegatecall manipulates transient storage in the context of the current address + {new ByteCodeBuilder() + .tstore(1, 8), + new ByteCodeBuilder() + .tstore(1, 7) + .call(Operation.DELEGATECALL, contractAddress, gasLimit) + .tload(1) + .dataOnStackToMemory(0) + .returnValueAtMemory(32, 0), + State.COMPLETED_SUCCESS, + 8}, + // Zeroing out a transient storage slot does not result in gas refund + {null, + new ByteCodeBuilder() + .tstore(1, 7) + .tstore(1, 0), + State.COMPLETED_SUCCESS, + 0}, + // Transient storage can be accessed in a static context when calling self + {new ByteCodeBuilder() + // Check if caller is self + .callerIs(contractAddress) + .conditionalJump(78) + + // Non-reentrant, call self after TSTORE 8 + .tstore(1, 8) + .call(Operation.STATICCALL, contractAddress, gasLimit) + .returnInnerCallResults() + + // Reentrant, TLOAD and return + .op(Operation.JUMPDEST) + .tload(1) + .dataOnStackToMemory(0) + .returnValueAtMemory(32, 0), + new ByteCodeBuilder() + .call(Operation.CALL, contractAddress, gasLimit) + .returnInnerCallResults(), + State.COMPLETED_SUCCESS, + 8} + }; + } + + private TestCodeExecutor codeExecutor; + + @Parameter() + public ByteCodeBuilder contractByteCodeBuilder; + + @Parameter(value = 1) + public ByteCodeBuilder byteCodeBuilder; + + @Parameter(value = 2) + public State expectedResultState; + + @Parameter(value = 3) + public int expectedReturnValue; + + + @Before + public void setUp() { + ProtocolSchedule protocolSchedule = MainnetProtocolSchedule.fromConfig( + new StubGenesisConfigOptions().eip1153Block(0), EvmConfiguration.DEFAULT); + codeExecutor = new TestCodeExecutor(protocolSchedule, + account -> account.setStorageValue(UInt256.ZERO, UInt256.valueOf(0))); + } + + @Test + public void transientStorageExecutionTest() { + // Pre-deploy the contract if it's specified + if (contractByteCodeBuilder != null) { + codeExecutor.deployContract( + contractAddress, + contractByteCodeBuilder.toString()); + } + + final MessageFrame frame = + codeExecutor.executeCode( + byteCodeBuilder.toString(), + gasLimit.toLong()); + assertThat(frame.getState()).isEqualTo(expectedResultState); + assertThat(frame.getGasRefund()).isEqualTo(0); + assertThat(UInt256.fromBytes(frame.getOutputData()).toInt()).isEqualTo(expectedReturnValue); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/operations/TStoreOperationTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/operations/TStoreOperationTest.java new file mode 100644 index 00000000000..844af39fdff --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/operations/TStoreOperationTest.java @@ -0,0 +1,193 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations; + +import org.apache.tuweni.units.bigints.UInt256; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.ethereum.core.MessageFrameTestFixture; +import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.EIP1153GasCalculator; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation.OperationResult; +import org.hyperledger.besu.evm.operation.TLoadOperation; +import org.hyperledger.besu.evm.operation.TStoreOperation; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.Optional; +import java.util.OptionalLong; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryWorldStateArchive; +import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.INSUFFICIENT_GAS; +import static org.mockito.Mockito.mock; + +public class TStoreOperationTest { + + private static final GasCalculator gasCalculator = new EIP1153GasCalculator(); + + private MessageFrame createMessageFrame( + final Address address, final long initialGas, final long remainingGas) { + final Blockchain blockchain = mock(Blockchain.class); + + final WorldStateArchive worldStateArchive = createInMemoryWorldStateArchive(); + final WorldUpdater worldStateUpdater = worldStateArchive.getMutable().updater(); + final BlockHeader blockHeader = new BlockHeaderTestFixture().buildHeader(); + final MessageFrame frame = + new MessageFrameTestFixture() + .address(address) + .worldUpdater(worldStateUpdater) + .blockHeader(blockHeader) + .blockchain(blockchain) + .initialGas(initialGas) + .build(); + worldStateUpdater.getOrCreate(address).getMutable().setBalance(Wei.of(1)); + worldStateUpdater.commit(); + frame.setGasRemaining(remainingGas); + + return frame; + } + + @Test + public void tstoreInsufficientGas() { + long initialGas = 10_000L; + long remainingGas = 99L; // TSTORE cost should be 100 + final TStoreOperation operation = new TStoreOperation(gasCalculator); + final MessageFrame frame = + createMessageFrame(Address.fromHexString("0x18675309"), initialGas, remainingGas); + frame.pushStackItem(UInt256.ZERO); + frame.pushStackItem(UInt256.fromHexString("0x01")); + + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(Optional.of(INSUFFICIENT_GAS)); + } + + @Test + public void tStoreSimpleTest() { + long initialGas = 10_000L; + long remainingGas = 10_000L; + final TStoreOperation operation = new TStoreOperation(gasCalculator); + final MessageFrame frame = + createMessageFrame(Address.fromHexString("0x18675309"), initialGas, remainingGas); + frame.pushStackItem(UInt256.ZERO); + frame.pushStackItem(UInt256.fromHexString("0x01")); + + final OperationResult result = operation.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(Optional.empty()); + } + + @Test + public void tLoadEmpty() { + long initialGas = 10_000L; + long remainingGas = 10_000L; + final MessageFrame frame = + createMessageFrame(Address.fromHexString("0x18675309"), initialGas, remainingGas); + + final TLoadOperation tload = new TLoadOperation(gasCalculator); + frame.pushStackItem(UInt256.fromHexString("0x01")); + final OperationResult tloadResult = tload.execute(frame, null); + assertThat(tloadResult.getHaltReason()).isEqualTo(Optional.empty()); + UInt256 tloadValue = UInt256.fromBytes(frame.popStackItem()); + assertThat(tloadValue).isEqualTo(UInt256.ZERO); + } + + @Test + public void tStoreTLoad() { + long initialGas = 10_000L; + long remainingGas = 10_000L; + final TStoreOperation tstore = new TStoreOperation(gasCalculator); + final MessageFrame frame = + createMessageFrame(Address.fromHexString("0x18675309"), initialGas, remainingGas); + frame.pushStackItem(UInt256.ONE); + frame.pushStackItem(UInt256.fromHexString("0x01")); + + final OperationResult result = tstore.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(Optional.empty()); + + TLoadOperation tload = new TLoadOperation(gasCalculator); + frame.pushStackItem(UInt256.fromHexString("0x01")); + OperationResult tloadResult = tload.execute(frame, null); + assertThat(tloadResult.getHaltReason()).isEqualTo(Optional.empty()); + UInt256 tloadValue = UInt256.fromBytes(frame.popStackItem()); + assertThat(tloadValue).isEqualTo(UInt256.ONE); + + // Loading from a different location returns default value + frame.pushStackItem(UInt256.fromHexString("0x02")); + tloadResult = tload.execute(frame, null); + assertThat(tloadResult.getHaltReason()).isEqualTo(Optional.empty()); + tloadValue = UInt256.fromBytes(frame.popStackItem()); + assertThat(tloadValue).isEqualTo(UInt256.ZERO); + } + + @Test + public void tStoreUpdate() { + long initialGas = 10_000L; + long remainingGas = 10_000L; + final TStoreOperation tstore = new TStoreOperation(gasCalculator); + final MessageFrame frame = + createMessageFrame(Address.fromHexString("0x18675309"), initialGas, remainingGas); + frame.pushStackItem(UInt256.ONE); + frame.pushStackItem(UInt256.fromHexString("0x01")); + + OperationResult result = tstore.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(Optional.empty()); + + // Store 2 at position 1 + frame.pushStackItem(UInt256.fromHexString("0x02")); + frame.pushStackItem(UInt256.fromHexString("0x01")); + + result = tstore.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(Optional.empty()); + + final TLoadOperation tload = new TLoadOperation(gasCalculator); + frame.pushStackItem(UInt256.fromHexString("0x01")); + final OperationResult tloadResult = tload.execute(frame, null); + assertThat(tloadResult.getHaltReason()).isEqualTo(Optional.empty()); + UInt256 tloadValue = UInt256.fromBytes(frame.popStackItem()); + assertThat(tloadValue).isEqualTo(UInt256.fromHexString("0x02")); + } + + // Zeroing out a transient storage slot does not result in gas refund + @Test + public void noGasRefundFromTransientState() { + long initialGas = 10_000L; + long remainingGas = 10_000L; + final TStoreOperation tstore = new TStoreOperation(gasCalculator); + final MessageFrame frame = + createMessageFrame(Address.fromHexString("0x18675309"), initialGas, remainingGas); + frame.pushStackItem(UInt256.ONE); + frame.pushStackItem(UInt256.fromHexString("0x01")); + + OperationResult result = tstore.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(Optional.empty()); + + // Reset value to 0 + frame.pushStackItem(UInt256.fromHexString("0x00")); + frame.pushStackItem(UInt256.fromHexString("0x01")); + + result = tstore.execute(frame, null); + assertThat(result.getHaltReason()).isEqualTo(Optional.empty()); + + assertThat(result.getGasCost()).isEqualTo(OptionalLong.of(100L)); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutableWorldStateTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutableWorldStateTest.java index 5745d24226e..1407ef3ee76 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutableWorldStateTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutableWorldStateTest.java @@ -674,4 +674,15 @@ public void shouldCombineUnchangedAndChangedValuesWhenRetrievingStorageEntries() worldState.persist(null); assertThat(worldState.get(ADDRESS).storageEntriesFrom(Hash.ZERO, 10)).isEqualTo(finalEntries); } + + @Test + public void setTransientStorageValue_ZeroValue() { + final MutableWorldState worldState = createEmpty(); + final WorldUpdater updater = worldState.updater(); + final MutableAccount account = updater.createAccount(ADDRESS).getMutable(); + account.setBalance(Wei.of(100000)); + account.setTransientStorageValue(UInt256.ZERO, UInt256.ZERO); + updater.commit(); + assertThat(worldState.get(ADDRESS).getTransientStorageValue(UInt256.ZERO)).isEqualTo(UInt256.ZERO); + } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/BlockPropagationManager.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/BlockPropagationManager.java index a459d2c6442..d017009ee36 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/BlockPropagationManager.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/BlockPropagationManager.java @@ -511,7 +511,8 @@ private String toLogString(final Collection newBlockHashs) { } private void reactToTTDReachedEvent(final boolean ttdReached) { - if (ttdReached) { + if (started.get() && ttdReached) { + LOG.info("Block propagation was running, then ttd reached, stopping"); stop(); } else if (!started.get()) { start(); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardChain.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardChain.java index db47c0b7c3c..8453476e732 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardChain.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardChain.java @@ -95,29 +95,13 @@ public synchronized void prependAncestorsHeader(final BlockHeader blockHeader) { return; } BlockHeader firstHeader = firstStoredAncestor.get(); - if (firstHeader.getNumber() != blockHeader.getNumber() + 1) { - throw new BackwardSyncException( - "Wrong height of header " - + blockHeader.getHash().toHexString() - + " is " - + blockHeader.getNumber() - + " when we were expecting " - + (firstHeader.getNumber() - 1)); - } - if (!firstHeader.getParentHash().equals(blockHeader.getHash())) { - throw new BackwardSyncException( - "Hash of header does not match our expectations, was " - + blockHeader.toLogString() - + " when we expected " - + firstHeader.getParentHash().toHexString()); - } headers.put(blockHeader.getHash(), blockHeader); chainStorage.put(blockHeader.getHash(), firstStoredAncestor.get().getHash()); firstStoredAncestor = Optional.of(blockHeader); debugLambda( LOG, "Added header {} on height {} to backward chain led by pivot {} on height {}", - () -> blockHeader.toLogString(), + blockHeader::toLogString, blockHeader::getNumber, () -> lastStoredPivot.orElseThrow().toLogString(), firstHeader::getNumber); @@ -188,7 +172,13 @@ public synchronized void addNewHash(final Hash newBlockHash) { } public synchronized Optional getFirstHashToAppend() { - return Optional.ofNullable(hashesToAppend.poll()); + return Optional.ofNullable(hashesToAppend.peek()); + } + + public synchronized void removeFromHashToAppend(final Hash hashToRemove) { + if (hashesToAppend.contains(hashToRemove)) { + hashesToAppend.remove(hashToRemove); + } } public void addBadChainToManager(final BadBlockManager badBlocksManager, final Hash hash) { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncContext.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncContext.java index 55e779a70fd..3a9c3afe286 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncContext.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncContext.java @@ -14,8 +14,8 @@ */ package org.hyperledger.besu.ethereum.eth.sync.backwardsync; +import static org.hyperledger.besu.util.FutureUtils.exceptionallyCompose; import static org.hyperledger.besu.util.Slf4jLambdaHelper.debugLambda; -import static org.hyperledger.besu.util.Slf4jLambdaHelper.infoLambda; import static org.hyperledger.besu.util.Slf4jLambdaHelper.traceLambda; import org.hyperledger.besu.datatypes.Hash; @@ -24,7 +24,6 @@ import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; -import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; @@ -35,7 +34,6 @@ import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; import java.util.stream.Stream; import com.google.common.annotations.VisibleForTesting; @@ -45,7 +43,9 @@ public class BackwardSyncContext { private static final Logger LOG = LoggerFactory.getLogger(BackwardSyncContext.class); public static final int BATCH_SIZE = 200; - private static final int MAX_RETRIES = 100; + private static final int DEFAULT_MAX_RETRIES = 20; + + private static final long DEFAULT_MILLIS_BETWEEN_RETRIES = 5000; protected final ProtocolContext protocolContext; private final ProtocolSchedule protocolSchedule; @@ -60,6 +60,10 @@ public class BackwardSyncContext { private Optional maybeFinalized = Optional.empty(); private Optional maybeHead = Optional.empty(); + private final int maxRetries; + + private final long millisBetweenRetries = DEFAULT_MILLIS_BETWEEN_RETRIES; + public BackwardSyncContext( final ProtocolContext protocolContext, final ProtocolSchedule protocolSchedule, @@ -67,6 +71,24 @@ public BackwardSyncContext( final EthContext ethContext, final SyncState syncState, final BackwardChain backwardChain) { + this( + protocolContext, + protocolSchedule, + metricsSystem, + ethContext, + syncState, + backwardChain, + DEFAULT_MAX_RETRIES); + } + + public BackwardSyncContext( + final ProtocolContext protocolContext, + final ProtocolSchedule protocolSchedule, + final MetricsSystem metricsSystem, + final EthContext ethContext, + final SyncState syncState, + final BackwardChain backwardChain, + final int maxRetries) { this.protocolContext = protocolContext; this.protocolSchedule = protocolSchedule; @@ -74,6 +96,7 @@ public BackwardSyncContext( this.metricsSystem = metricsSystem; this.syncState = syncState; this.backwardChain = backwardChain; + this.maxRetries = maxRetries; } public synchronized boolean isSyncing() { @@ -96,27 +119,33 @@ public synchronized void updateHeads(final Hash head, final Hash finalizedBlockH } public synchronized CompletableFuture syncBackwardsUntil(final Hash newBlockHash) { - final CompletableFuture future = this.currentBackwardSyncFuture.get(); - if (isTrusted(newBlockHash)) return future; - backwardChain.addNewHash(newBlockHash); - if (future != null) { - return future; + Optional> maybeFuture = + Optional.ofNullable(this.currentBackwardSyncFuture.get()); + if (isTrusted(newBlockHash)) { + return maybeFuture.orElseGet(() -> CompletableFuture.completedFuture(null)); } - infoLambda(LOG, "Starting new backward sync towards a pivot {}", newBlockHash::toHexString); - this.currentBackwardSyncFuture.set(prepareBackwardSyncFutureWithRetry()); - return this.currentBackwardSyncFuture.get(); + backwardChain.addNewHash(newBlockHash); + return maybeFuture.orElseGet( + () -> { + CompletableFuture future = prepareBackwardSyncFutureWithRetry(); + this.currentBackwardSyncFuture.set(future); + return future; + }); } public synchronized CompletableFuture syncBackwardsUntil(final Block newPivot) { - final CompletableFuture future = this.currentBackwardSyncFuture.get(); - if (isTrusted(newPivot.getHash())) return future; - backwardChain.appendTrustedBlock(newPivot); - if (future != null) { - return future; + Optional> maybeFuture = + Optional.ofNullable(this.currentBackwardSyncFuture.get()); + if (isTrusted(newPivot.getHash())) { + return maybeFuture.orElseGet(() -> CompletableFuture.completedFuture(null)); } - infoLambda(LOG, "Starting new backward sync towards a pivot {}", newPivot::toLogString); - this.currentBackwardSyncFuture.set(prepareBackwardSyncFutureWithRetry()); - return this.currentBackwardSyncFuture.get(); + backwardChain.appendTrustedBlock(newPivot); + return maybeFuture.orElseGet( + () -> { + CompletableFuture future = prepareBackwardSyncFutureWithRetry(); + this.currentBackwardSyncFuture.set(future); + return future; + }); } private boolean isTrusted(final Hash hash) { @@ -131,54 +160,77 @@ private boolean isTrusted(final Hash hash) { } private CompletableFuture prepareBackwardSyncFutureWithRetry() { + return prepareBackwardSyncFutureWithRetry(maxRetries) + .handle( + (unused, throwable) -> { + this.currentBackwardSyncFuture.set(null); + if (throwable != null) { + throw extractBackwardSyncException(throwable) + .orElse(new BackwardSyncException(throwable)); + } + return null; + }); + } - CompletableFuture f = prepareBackwardSyncFuture(); - for (int i = 0; i < MAX_RETRIES; i++) { - f = - f.thenApply(CompletableFuture::completedFuture) - .exceptionally( - ex -> { - processException(ex); - return ethContext - .getScheduler() - .scheduleFutureTask(this::prepareBackwardSyncFuture, Duration.ofSeconds(5)); - }) - .thenCompose(Function.identity()); + private CompletableFuture prepareBackwardSyncFutureWithRetry(final int retries) { + if (retries == 0) { + return CompletableFuture.failedFuture( + new BackwardSyncException("Max number of retries " + maxRetries + " reached")); } - return f.handle( - (unused, throwable) -> { - this.currentBackwardSyncFuture.set(null); - if (throwable != null) { - throw new BackwardSyncException(throwable); - } - return null; + + return exceptionallyCompose( + prepareBackwardSyncFuture(), + throwable -> { + processException(throwable); + return ethContext + .getScheduler() + .scheduleFutureTask( + () -> prepareBackwardSyncFutureWithRetry(retries - 1), + Duration.ofMillis(millisBetweenRetries)); }); } @VisibleForTesting protected void processException(final Throwable throwable) { + extractBackwardSyncException(throwable) + .ifPresentOrElse( + backwardSyncException -> { + if (backwardSyncException.shouldRestart()) { + LOG.info( + "Backward sync failed ({}). Current Peers: {}. Retrying in " + + millisBetweenRetries + + " milliseconds...", + backwardSyncException.getMessage(), + ethContext.getEthPeers().peerCount()); + return; + } else { + debugLambda( + LOG, "Not recoverable backward sync exception {}", throwable::getMessage); + throw backwardSyncException; + } + }, + () -> + LOG.warn( + "There was an uncaught exception during Backwards Sync. Retrying in " + + millisBetweenRetries + + " milliseconds...", + throwable)); + } + + private Optional extractBackwardSyncException(final Throwable throwable) { Throwable currentCause = throwable; while (currentCause != null) { if (currentCause instanceof BackwardSyncException) { - if (((BackwardSyncException) currentCause).shouldRestart()) { - LOG.info( - "Backward sync failed ({}). Current Peers: {}. Retrying in few seconds... ", - currentCause.getMessage(), - ethContext.getEthPeers().peerCount()); - return; - } else { - throw new BackwardSyncException(throwable); - } + return Optional.of((BackwardSyncException) currentCause); } currentCause = currentCause.getCause(); } - LOG.warn( - "There was an uncaught exception during Backwards Sync... Retrying in few seconds...", - throwable); + return Optional.empty(); } - private CompletableFuture prepareBackwardSyncFuture() { + @VisibleForTesting + CompletableFuture prepareBackwardSyncFuture() { final MutableBlockchain blockchain = getProtocolContext().getBlockchain(); return new BackwardsSyncAlgorithm( this, @@ -237,7 +289,6 @@ public void resetBatchSize() { protected Void saveBlock(final Block block) { traceLambda(LOG, "Going to validate block {}", block::toLogString); - checkFinalizedSuccessionRuleBeforeSave(block); var optResult = this.getBlockValidatorForBlock(block) .validateAndProcessBlock( @@ -269,63 +320,6 @@ protected Void saveBlock(final Block block) { return null; } - @VisibleForTesting - protected synchronized void checkFinalizedSuccessionRuleBeforeSave(final Block block) { - final Optional finalized = findMaybeFinalized(); - if (finalized.isPresent()) { - final Optional maybeFinalizedHeader = - protocolContext - .getBlockchain() - .getBlockByHash(finalized.get()) - .map(Block::getHeader) - .or(() -> backwardChain.getHeader(finalized.get())); - if (maybeFinalizedHeader.isEmpty()) { - throw new BackwardSyncException( - "We know a block " - + finalized.get().toHexString() - + " was finalized, but we don't have it downloaded yet, cannot save new block", - true); - } - final BlockHeader finalizedHeader = maybeFinalizedHeader.get(); - if (finalizedHeader.getHash().equals(block.getHash())) { - debugLambda(LOG, "Saving new finalized block {}", block::toLogString); - return; - } - - if (finalizedHeader.getNumber() == block.getHeader().getNumber()) { - throw new BackwardSyncException( - "This block is not the target finalized block. Is " - + block.toLogString() - + " but was expecting " - + finalizedHeader.toLogString()); - } - if (!getProtocolContext().getBlockchain().contains(finalizedHeader.getHash())) { - debugLambda( - LOG, - "Saving block {} before finalized {} reached", - block::toLogString, - finalizedHeader::toLogString); // todo: some check here?? - return; - } - final Hash canonicalHash = - getProtocolContext() - .getBlockchain() - .getBlockByNumber(finalizedHeader.getNumber()) - .orElseThrow() - .getHash(); - if (finalizedHeader.getNumber() < block.getHeader().getNumber() - && !canonicalHash.equals(finalizedHeader.getHash())) { - throw new BackwardSyncException( - "Finalized block " - + finalizedHeader.toLogString() - + " is not on canonical chain. Canonical is" - + canonicalHash.toHexString() - + ". We need to reorg before saving this block."); - } - } - LOG.debug("Finalized block not known yet..."); - } - @VisibleForTesting protected void possiblyMoveHead(final Block lastSavedBlock) { final MutableBlockchain blockchain = getProtocolContext().getBlockchain(); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardsSyncAlgorithm.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardsSyncAlgorithm.java index 9c1551b15a3..845c117e9c0 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardsSyncAlgorithm.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardsSyncAlgorithm.java @@ -56,7 +56,13 @@ public CompletableFuture executeBackwardsSync(final Void unused) { public CompletableFuture pickNextStep() { final Optional firstHash = context.getBackwardChain().getFirstHashToAppend(); if (firstHash.isPresent()) { - return executeSyncStep(firstHash.get()); + return executeSyncStep(firstHash.get()) + .whenComplete( + (result, throwable) -> { + if (throwable == null) { + context.getBackwardChain().removeFromHashToAppend(firstHash.get()); + } + }); } if (!context.isReady()) { return waitForReady(); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncContextTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncContextTest.java index dd5e76bf94d..57bb2345a65 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncContextTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncContextTest.java @@ -54,6 +54,7 @@ import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import javax.annotation.Nonnull; import org.junit.Before; @@ -72,6 +73,8 @@ public class BackwardSyncContextTest { public static final int LOCAL_HEIGHT = 25; public static final int UNCLE_HEIGHT = 25 - 3; + public static final int NUM_OF_RETRIES = 100; + private BackwardSyncContext context; private MutableBlockchain remoteBlockchain; @@ -157,7 +160,8 @@ public void setup() { metricsSystem, ethContext, syncState, - backwardChain)); + backwardChain, + NUM_OF_RETRIES)); doReturn(true).when(context).isReady(); doReturn(2).when(context).getBatchSize(); } @@ -258,63 +262,6 @@ public void testUpdatingHead() { assertThat(localBlockchain.getChainHeadBlock().getHeader().getNumber()).isEqualTo(4); } - @Test - public void testSuccessionRuleAfterUpdatingFinalized() { - - backwardChain.appendTrustedBlock( - remoteBlockchain.getBlockByNumber(LOCAL_HEIGHT + 1).orElseThrow()); - // null check - context.updateHeads(null, null); - context.checkFinalizedSuccessionRuleBeforeSave(null); - // zero check - context.updateHeads(null, Hash.ZERO); - context.checkFinalizedSuccessionRuleBeforeSave(null); - - // cannot save if we don't know what is finalized - context.updateHeads( - null, remoteBlockchain.getBlockHashByNumber(LOCAL_HEIGHT + 10).orElseThrow()); - assertThatThrownBy(() -> context.checkFinalizedSuccessionRuleBeforeSave(null)) - .isInstanceOf(BackwardSyncException.class) - .hasMessageContaining( - "was finalized, but we don't have it downloaded yet, cannot save new block"); - - // updating with new finalized - context.updateHeads( - null, remoteBlockchain.getBlockHashByNumber(LOCAL_HEIGHT + 1).orElseThrow()); - context.checkFinalizedSuccessionRuleBeforeSave( - remoteBlockchain.getBlockByNumber(LOCAL_HEIGHT + 1).orElseThrow()); - - // updating when we know finalized is in futre - context.updateHeads( - null, remoteBlockchain.getBlockHashByNumber(LOCAL_HEIGHT + 4).orElseThrow()); - context.checkFinalizedSuccessionRuleBeforeSave( - remoteBlockchain.getBlockByNumber(LOCAL_HEIGHT + 1).orElseThrow()); - - // updating with block that is not finalized when we expected finalized on this height - context.updateHeads( - null, remoteBlockchain.getBlockHashByNumber(LOCAL_HEIGHT + 1).orElseThrow()); - assertThatThrownBy( - () -> - context.checkFinalizedSuccessionRuleBeforeSave( - createUncle(LOCAL_HEIGHT + 1, localBlockchain.getChainHeadHash()))) - .isInstanceOf(BackwardSyncException.class) - .hasMessageContaining("This block is not the target finalized block"); - - // updating with a block when finalized is not on canonical chain - context.updateHeads(null, uncle.getHash()); - assertThatThrownBy( - () -> - context.checkFinalizedSuccessionRuleBeforeSave( - remoteBlockchain.getBlockByNumber(LOCAL_HEIGHT + 1).orElseThrow())) - .isInstanceOf(BackwardSyncException.class) - .hasMessageContaining("is not on canonical chain. Canonical is"); - - // updating when finalized is on canonical chain - context.updateHeads(null, localBlockchain.getBlockHashByNumber(UNCLE_HEIGHT).orElseThrow()); - context.checkFinalizedSuccessionRuleBeforeSave( - remoteBlockchain.getBlockByNumber(LOCAL_HEIGHT + 1).orElseThrow()); - } - @Test public void shouldProcessExceptionsCorrectly() { assertThatThrownBy( @@ -345,4 +292,23 @@ public void makeSureWeRememberBadBlocks() { Mockito.verify(manager).addBadBlock(block); } + + @Test + public void shouldFailAfterMaxNumberOfRetries() { + doReturn(CompletableFuture.failedFuture(new Exception())) + .when(context) + .prepareBackwardSyncFuture(); + + final var syncFuture = context.syncBackwardsUntil(Hash.ZERO); + + try { + syncFuture.get(); + } catch (final Throwable throwable) { + if (throwable instanceof ExecutionException) { + BackwardSyncException backwardSyncException = (BackwardSyncException) throwable.getCause(); + assertThat(backwardSyncException.getMessage()) + .contains("Max number of retries " + NUM_OF_RETRIES + " reached"); + } + } + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/InMemoryBackwardChainTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/InMemoryBackwardChainTest.java index 3ea0cccfdb7..3fabb7bad83 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/InMemoryBackwardChainTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/InMemoryBackwardChainTest.java @@ -15,9 +15,7 @@ package org.hyperledger.besu.ethereum.eth.sync.backwardsync; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.hyperledger.besu.ethereum.eth.sync.backwardsync.ChainForTestCreator.prepareChain; -import static org.hyperledger.besu.ethereum.eth.sync.backwardsync.ChainForTestCreator.prepareWrongParentHash; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.core.Block; @@ -95,32 +93,6 @@ public void shouldSaveHeadersWhenHeightAndHashMatches() { assertThat(firstHeader).isEqualTo(blocks.get(blocks.size() - 4).getHeader()); } - @Test - public void shouldNotSaveHeadersWhenWrongHeight() { - BackwardChain backwardChain = createChainFromBlock(blocks.get(blocks.size() - 1)); - backwardChain.prependAncestorsHeader(blocks.get(blocks.size() - 2).getHeader()); - backwardChain.prependAncestorsHeader(blocks.get(blocks.size() - 3).getHeader()); - assertThatThrownBy( - () -> backwardChain.prependAncestorsHeader(blocks.get(blocks.size() - 5).getHeader())) - .isInstanceOf(BackwardSyncException.class) - .hasMessageContaining("Wrong height of header"); - BlockHeader firstHeader = backwardChain.getFirstAncestorHeader().orElseThrow(); - assertThat(firstHeader).isEqualTo(blocks.get(blocks.size() - 3).getHeader()); - } - - @Test - public void shouldNotSaveHeadersWhenWrongHash() { - BackwardChain backwardChain = createChainFromBlock(blocks.get(blocks.size() - 1)); - backwardChain.prependAncestorsHeader(blocks.get(blocks.size() - 2).getHeader()); - backwardChain.prependAncestorsHeader(blocks.get(blocks.size() - 3).getHeader()); - BlockHeader wrongHashHeader = prepareWrongParentHash(blocks.get(blocks.size() - 4).getHeader()); - assertThatThrownBy(() -> backwardChain.prependAncestorsHeader(wrongHashHeader)) - .isInstanceOf(BackwardSyncException.class) - .hasMessageContaining("Hash of header does not match our expectations"); - BlockHeader firstHeader = backwardChain.getFirstAncestorHeader().orElseThrow(); - assertThat(firstHeader).isEqualTo(blocks.get(blocks.size() - 3).getHeader()); - } - @Test public void shouldDropFromTheEnd() { @@ -166,12 +138,15 @@ public void shouldAddHeaderToQueue() { firstHash = backwardChain.getFirstHashToAppend(); assertThat(firstHash).isPresent(); assertThat(firstHash.orElseThrow()).isEqualTo(blocks.get(7).getHash()); + backwardChain.removeFromHashToAppend(firstHash.get()); firstHash = backwardChain.getFirstHashToAppend(); assertThat(firstHash).isPresent(); assertThat(firstHash.orElseThrow()).isEqualTo(blocks.get(9).getHash()); + backwardChain.removeFromHashToAppend(firstHash.get()); firstHash = backwardChain.getFirstHashToAppend(); assertThat(firstHash).isPresent(); assertThat(firstHash.orElseThrow()).isEqualTo(blocks.get(11).getHash()); + backwardChain.removeFromHashToAppend(firstHash.get()); } @Test diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/config/DiscoveryConfiguration.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/config/DiscoveryConfiguration.java index 9fa25637502..3b4bdb8f36d 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/config/DiscoveryConfiguration.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/config/DiscoveryConfiguration.java @@ -31,6 +31,7 @@ public class DiscoveryConfiguration { private int bucketSize = 16; private List bootnodes = new ArrayList<>(); private String dnsDiscoveryURL; + private boolean discoveryV5Enabled = false; public static DiscoveryConfiguration create() { return new DiscoveryConfiguration(); @@ -113,6 +114,14 @@ public DiscoveryConfiguration setDnsDiscoveryURL(final String dnsDiscoveryURL) { return this; } + public boolean isDiscoveryV5Enabled() { + return discoveryV5Enabled; + } + + public void setDiscoveryV5Enabled(final boolean discoveryV5Enabled) { + this.discoveryV5Enabled = discoveryV5Enabled; + } + @Override public boolean equals(final Object o) { if (o == this) { diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetwork.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetwork.java index e51e3b44e8f..afbf7872837 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetwork.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetwork.java @@ -204,6 +204,10 @@ public void start() { return; } + if (config.getDiscovery().isDiscoveryV5Enabled()) { + LOG.warn("Discovery Protocol v5 is not available"); + } + final String address = config.getDiscovery().getAdvertisedHost(); final int configuredDiscoveryPort = config.getDiscovery().getBindPort(); final int configuredRlpxPort = config.getRlpx().getBindPort(); diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/NetworkingServiceLifecycleTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/NetworkingServiceLifecycleTest.java index 715eb431435..96340b7705a 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/NetworkingServiceLifecycleTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/NetworkingServiceLifecycleTest.java @@ -17,7 +17,9 @@ import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.hamcrest.Matchers.startsWith; import static org.hyperledger.besu.ethereum.p2p.NetworkingTestHelper.configWithRandomPorts; +import static org.junit.Assume.assumeThat; import org.hyperledger.besu.crypto.NodeKey; import org.hyperledger.besu.crypto.NodeKeyUtils; @@ -139,6 +141,10 @@ public void startDiscoveryAgentBackToBack() throws IOException { @Test public void startDiscoveryPortInUse() throws IOException { + assumeThat( + "Ignored if system language is not English", + System.getProperty("user.language"), + startsWith("en")); try (final P2PNetwork service1 = builder().config(config).build()) { service1.start(); final NetworkingConfiguration config = configWithRandomPorts(); diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java index c277f28e1e7..690807007e2 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java @@ -68,6 +68,7 @@ public static ReferenceTestProtocolSchedules create() { builder.put( "ArrowGlacier", createSchedule(new StubGenesisConfigOptions().arrowGlacierBlock(0))); builder.put("GrayGlacier", createSchedule(new StubGenesisConfigOptions().grayGlacierBlock(0))); + builder.put("EIP1153", createSchedule(new StubGenesisConfigOptions().eip1153Block(0))); return new ReferenceTestProtocolSchedules(builder.build()); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java b/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java index adbe751da55..3c3f26bfa5a 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java @@ -17,6 +17,7 @@ import org.hyperledger.besu.evm.gascalculator.BerlinGasCalculator; import org.hyperledger.besu.evm.gascalculator.ByzantiumGasCalculator; import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator; +import org.hyperledger.besu.evm.gascalculator.EIP1153GasCalculator; import org.hyperledger.besu.evm.gascalculator.FrontierGasCalculator; import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.gascalculator.IstanbulGasCalculator; @@ -104,6 +105,8 @@ import org.hyperledger.besu.evm.operation.SubOperation; import org.hyperledger.besu.evm.operation.SwapOperation; import org.hyperledger.besu.evm.operation.TimestampOperation; +import org.hyperledger.besu.evm.operation.TLoadOperation; +import org.hyperledger.besu.evm.operation.TStoreOperation; import org.hyperledger.besu.evm.operation.XorOperation; import java.math.BigInteger; @@ -394,4 +397,32 @@ public static void registerParisOperations( registerLondonOperations(registry, gasCalculator, chainID); registry.put(new PrevRanDaoOperation(gasCalculator)); } + + // TODO EIP-1153 change for the actual fork name when known + public static EVM eip1153(final BigInteger chainId, final EvmConfiguration evmConfiguration) { + return eip1153(new EIP1153GasCalculator(), chainId, evmConfiguration); + } + + public static EVM eip1153( + final GasCalculator gasCalculator, + final BigInteger chainId, + final EvmConfiguration evmConfiguration) { + return new EVM(eip1153Operations(gasCalculator, chainId), gasCalculator, evmConfiguration); + } + + public static OperationRegistry eip1153Operations( + final GasCalculator gasCalculator, final BigInteger chainId) { + OperationRegistry operationRegistry = new OperationRegistry(); + registerEIP1153Operations(operationRegistry, gasCalculator, chainId); + return operationRegistry; + } + + public static void registerEIP1153Operations( + final OperationRegistry registry, + final GasCalculator gasCalculator, + final BigInteger chainID) { + registerParisOperations(registry, gasCalculator, chainID); + registry.put(new TStoreOperation(gasCalculator)); + registry.put(new TLoadOperation(gasCalculator)); + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/account/AccountState.java b/evm/src/main/java/org/hyperledger/besu/evm/account/AccountState.java index a1bd0da1826..d00807dec0f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/account/AccountState.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/account/AccountState.java @@ -112,6 +112,15 @@ default boolean hasCode() { */ UInt256 getOriginalStorageValue(UInt256 key); + /** + * Retrieves a value in the account transient storage given its key. + * + * @param key the key to retrieve in the account storage. + * @return the value associated to {@code key} in the account storage. Note that this is never + * {@code null}, but 0 acts as a default value. + */ + UInt256 getTransientStorageValue(UInt256 key); + /** * Whether the account is "empty". * diff --git a/evm/src/main/java/org/hyperledger/besu/evm/account/MutableAccount.java b/evm/src/main/java/org/hyperledger/besu/evm/account/MutableAccount.java index a4caf35c288..6908421b8b2 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/account/MutableAccount.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/account/MutableAccount.java @@ -96,6 +96,16 @@ default Wei decrementBalance(final Wei value) { */ void setStorageValue(UInt256 key, UInt256 value); + /** + * Sets a particular key-value pair in the account transient storage. + * + *

Note that setting the value of an entry to 0 is basically equivalent to deleting that entry. + * + * @param key the key to set. + * @param value the value to set {@code key} to. + */ + void setTransientStorageValue(UInt256 key, UInt256 value); + /** Clears out an account's storage. */ void clearStorage(); @@ -105,4 +115,11 @@ default Wei decrementBalance(final Wei value) { * @return a map of storage that has been modified. */ Map getUpdatedStorage(); + + /** + * Returns the transient storage entries that have been set through the updater this instance came from. + * + * @return a map of transient storage that has been modified. + */ + Map getUpdatedTransientStorage(); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleAccount.java b/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleAccount.java index 76b526c20f7..56aa1b641a8 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleAccount.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleAccount.java @@ -47,6 +47,7 @@ public class SimpleAccount implements EvmAccount, MutableAccount { private Supplier codeHash = Suppliers.memoize(() -> code == null ? Hash.EMPTY : Hash.hash(code)); private final Map storage = new HashMap<>(); + private final Map transientStorage = new HashMap<>(); public SimpleAccount(final Address address, final long nonce, final Wei balance) { this(null, address, nonce, balance, Bytes.EMPTY); @@ -155,4 +156,23 @@ public void clearStorage() { public Map getUpdatedStorage() { return storage; } + + @Override + public Map getUpdatedTransientStorage() { + return transientStorage; + } + + @Override + public UInt256 getTransientStorageValue(final UInt256 key) { + if (transientStorage.containsKey(key)) { + return transientStorage.get(key); + } else { + return UInt256.ZERO; + } + } + + @Override + public void setTransientStorageValue(final UInt256 key, final UInt256 value) { + transientStorage.put(key, value); + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java index 3cfe8d5a634..d508675d6e9 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java @@ -240,6 +240,7 @@ public enum Type { private final Consumer completer; private Optional maybeUpdatedMemory = Optional.empty(); private Optional maybeUpdatedStorage = Optional.empty(); + private Optional maybeUpdatedTransientStorage = Optional.empty(); public static Builder builder() { return new Builder(); @@ -722,6 +723,11 @@ private void setUpdatedMemory(final long offset, final Bytes value) { public void storageWasUpdated(final UInt256 storageAddress, final Bytes value) { maybeUpdatedStorage = Optional.of(new StorageEntry(storageAddress, value)); } + + public void transientStorageWasUpdated(final UInt256 storageAddress, final Bytes value) { + maybeUpdatedTransientStorage = Optional.of(new StorageEntry(storageAddress, value)); + } + /** * Accumulate a log. * @@ -1093,9 +1099,14 @@ public Optional getMaybeUpdatedStorage() { return maybeUpdatedStorage; } + public Optional getMaybeUpdatedTransientStorage() { + return maybeUpdatedTransientStorage; + } + public void reset() { maybeUpdatedMemory = Optional.empty(); maybeUpdatedStorage = Optional.empty(); + maybeUpdatedTransientStorage = Optional.empty(); } public static class Builder { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/BerlinGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/BerlinGasCalculator.java index 9ef385ce66e..522fffbf6ec 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/BerlinGasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/BerlinGasCalculator.java @@ -33,9 +33,9 @@ public class BerlinGasCalculator extends IstanbulGasCalculator { // new constants for EIP-2929 private static final long COLD_SLOAD_COST = 2100L; private static final long COLD_ACCOUNT_ACCESS_COST = 2600L; - private static final long WARM_STORAGE_READ_COST = 100L; private static final long ACCESS_LIST_ADDRESS_COST = 2400L; protected static final long ACCESS_LIST_STORAGE_COST = 1900L; + protected static final long WARM_STORAGE_READ_COST = 100L; // redefinitions for EIP-2929 private static final long SLOAD_GAS = WARM_STORAGE_READ_COST; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/EIP1153GasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/EIP1153GasCalculator.java new file mode 100644 index 00000000000..d31c41f15d0 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/EIP1153GasCalculator.java @@ -0,0 +1,34 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.gascalculator; + +public class EIP1153GasCalculator extends LondonGasCalculator { + + private static final long TLOAD_GAS = WARM_STORAGE_READ_COST; + private static final long TSTORE_GAS = WARM_STORAGE_READ_COST; + + public EIP1153GasCalculator() {} + + // EIP-1153 + @Override + public long getTransientLoadOperationGasCost() { + return TLOAD_GAS; + } + + @Override + public long getTransientStoreOperationGasCost() { + return TSTORE_GAS; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java index 0d882248454..bac66066f17 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java @@ -473,4 +473,22 @@ default long getMaxRefundQuotient() { */ // what would be the gas for a PMT with hash of all non-zeros long getMaximumTransactionCost(int size); + + /** + * Returns the cost of a loading from Transient Storage + * + * @return the cost of a TLOAD from a storage slot + */ + default long getTransientLoadOperationGasCost() { + return 0L; + } + + /** + * Returns the cost of a storing to Transient Storage + * + * @return the cost of a TSTORE to a storage slot + */ + default long getTransientStoreOperationGasCost() { + return 0L; + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractOperation.java index 2eb3f4f796b..6580bcd2356 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractOperation.java @@ -14,8 +14,12 @@ */ package org.hyperledger.besu.evm.operation; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import java.util.Optional; +import java.util.OptionalLong; + /** * All {@link Operation} implementations should inherit from this class to get the setting of some * members for free. @@ -29,6 +33,10 @@ public abstract class AbstractOperation implements Operation { private final int opSize; private final GasCalculator gasCalculator; + protected static final OperationResult ILLEGAL_STATE_CHANGE = + new OperationResult( + OptionalLong.of(0L), Optional.of(ExceptionalHaltReason.ILLEGAL_STATE_CHANGE)); + protected AbstractOperation( final int opcode, final String name, diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/SStoreOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SStoreOperation.java index cbfd3c8e0a7..f2a011448ec 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SStoreOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SStoreOperation.java @@ -31,10 +31,6 @@ public class SStoreOperation extends AbstractOperation { public static final long FRONTIER_MINIMUM = 0L; public static final long EIP_1706_MINIMUM = 2300L; - protected static final OperationResult ILLEGAL_STATE_CHANGE = - new OperationResult( - OptionalLong.of(0L), Optional.of(ExceptionalHaltReason.ILLEGAL_STATE_CHANGE)); - private final long minimumGasRemaining; public SStoreOperation(final GasCalculator gasCalculator, final long minimumGasRemaining) { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/TLoadOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/TLoadOperation.java new file mode 100644 index 00000000000..1879bb6ef88 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/TLoadOperation.java @@ -0,0 +1,59 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.internal.FixedStack.OverflowException; +import org.hyperledger.besu.evm.internal.FixedStack.UnderflowException; + +import java.util.Optional; +import java.util.OptionalLong; + +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; + +public class TLoadOperation extends AbstractOperation { + + public TLoadOperation(final GasCalculator gasCalculator) { + super(0xb3, "TLOAD", 1, 1, 1, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + final long cost = gasCalculator().getTransientLoadOperationGasCost(); + try { + final Account account = frame.getWorldUpdater().get(frame.getRecipientAddress()); + final Bytes32 key = UInt256.fromBytes(frame.popStackItem()); + if (frame.getRemainingGas() < cost) { + return new OperationResult( + OptionalLong.of(cost), Optional.of(ExceptionalHaltReason.INSUFFICIENT_GAS)); + } else { + frame.pushStackItem(account.getTransientStorageValue(UInt256.fromBytes(key))); + + return new OperationResult(OptionalLong.of(cost), Optional.empty()); + } + } catch (final UnderflowException ufe) { + return new OperationResult( + OptionalLong.of(cost), Optional.of(ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS)); + } catch (final OverflowException ofe) { + return new OperationResult(OptionalLong.of(cost), Optional.of(ExceptionalHaltReason.TOO_MANY_STACK_ITEMS)); + } + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/TStoreOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/TStoreOperation.java new file mode 100644 index 00000000000..5b1b2a85db3 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/TStoreOperation.java @@ -0,0 +1,61 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import java.util.Optional; +import java.util.OptionalLong; + +import org.apache.tuweni.units.bigints.UInt256; + +public class TStoreOperation extends AbstractOperation { + + public TStoreOperation(final GasCalculator gasCalculator) { + super(0xb4, "TSTORE", 2, 0, 1, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + + final UInt256 key = UInt256.fromBytes(frame.popStackItem()); + final UInt256 value = UInt256.fromBytes(frame.popStackItem()); + + final MutableAccount account = + frame.getWorldUpdater().getAccount(frame.getRecipientAddress()).getMutable(); + if (account == null) { + return ILLEGAL_STATE_CHANGE; + } + + final long cost = gasCalculator().getTransientStoreOperationGasCost(); + + final long remainingGas = frame.getRemainingGas(); + if (frame.isStatic()) { + return new OperationResult( + OptionalLong.of(remainingGas), Optional.of(ExceptionalHaltReason.ILLEGAL_STATE_CHANGE)); + } else if (remainingGas < cost) { + return new OperationResult( + OptionalLong.of(cost), Optional.of(ExceptionalHaltReason.INSUFFICIENT_GAS)); + } else { + account.setTransientStorageValue(key, value); + frame.transientStorageWasUpdated(key, value); + return new OperationResult(OptionalLong.of(cost), Optional.empty()); + } + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/StackedUpdater.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/StackedUpdater.java index 066dd449eb9..57f0f1981fb 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/StackedUpdater.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/StackedUpdater.java @@ -94,6 +94,7 @@ public void commit() { existing.clearStorage(); } update.getUpdatedStorage().forEach(existing::setStorageValue); + update.getUpdatedTransientStorage().forEach(existing::setTransientStorageValue); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/UpdateTrackingAccount.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/UpdateTrackingAccount.java index d761e05251e..a7c07bb130f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/UpdateTrackingAccount.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/UpdateTrackingAccount.java @@ -27,6 +27,7 @@ import org.hyperledger.besu.evm.account.EvmAccount; import org.hyperledger.besu.evm.account.MutableAccount; +import java.util.HashMap; import java.util.Map; import java.util.NavigableMap; import java.util.TreeMap; @@ -42,7 +43,7 @@ * *

Note that in practice this only track the modified value of the nonce and balance, but doesn't * remind if those were modified or not (the reason being that any modification of an account imply - * the underlying trie node will have to be updated, and so knowing if the nonce and balance where + * the underlying trie node will have to be updated, and so knowing if the nonce and balance were * updated or not doesn't matter, we just need their new value). */ public class UpdateTrackingAccount implements MutableAccount, EvmAccount { @@ -62,6 +63,7 @@ public class UpdateTrackingAccount implements MutableAccount, private final NavigableMap updatedStorage; private boolean storageWasCleared = false; private boolean transactionBoundary = false; + private final Map updatedTransientStorage; UpdateTrackingAccount(final Address address) { checkNotNull(address); @@ -74,6 +76,7 @@ public class UpdateTrackingAccount implements MutableAccount, this.updatedCode = Bytes.EMPTY; this.updatedStorage = new TreeMap<>(); + this.updatedTransientStorage = new HashMap<>(); } public UpdateTrackingAccount(final A account) { @@ -90,6 +93,7 @@ public UpdateTrackingAccount(final A account) { this.balance = account.getBalance(); this.updatedStorage = new TreeMap<>(); + this.updatedTransientStorage = new HashMap<>(); } /** @@ -131,6 +135,17 @@ public Map getUpdatedStorage() { return updatedStorage; } + /** + * A map of the transient storage entries that were modified. + * + * @return a map containing all entries that have been modified. This may contain entries + * with a value of 0 to signify deletion. + */ + @Override + public Map getUpdatedTransientStorage() { + return updatedTransientStorage; + } + @Override public Address getAddress() { return address; @@ -254,6 +269,7 @@ public void setStorageValue(final UInt256 key, final UInt256 value) { public void clearStorage() { storageWasCleared = true; updatedStorage.clear(); + updatedTransientStorage.clear(); } public boolean getStorageWasCleared() { @@ -267,16 +283,38 @@ public void setStorageWasCleared(final boolean storageWasCleared) { @Override public String toString() { String storage = updatedStorage.isEmpty() ? "[not updated]" : updatedStorage.toString(); + String transientStorage = updatedTransientStorage.isEmpty() ? "[not updated]" : updatedTransientStorage.toString(); if (updatedStorage.isEmpty() && storageWasCleared) { storage = "[cleared]"; + transientStorage = "[cleared]"; } return String.format( - "%s -> {nonce: %s, balance:%s, code:%s, storage:%s }", - address, nonce, balance, updatedCode == null ? "[not updated]" : updatedCode, storage); + "%s -> {nonce: %s, balance:%s, code:%s, storage:%s, transientStorage:%s }", + address, nonce, balance, updatedCode == null ? "[not updated]" : updatedCode, storage, transientStorage); } @Override public MutableAccount getMutable() throws ModificationNotAllowedException { return this; } + + @Override + public UInt256 getTransientStorageValue(final UInt256 key) { + final UInt256 value = updatedTransientStorage.get(key); + if (value != null) { + return value; + } + if (storageWasCleared) { + return UInt256.ZERO; + } + + // We haven't updated the key-value yet, so either it's a new account and it doesn't have the + // key, or we should query the underlying storage for its existing value (which might be 0). + return account == null ? UInt256.ZERO : account.getTransientStorageValue(key); + } + + @Override + public void setTransientStorageValue(final UInt256 key, final UInt256 value) { + updatedTransientStorage.put(key, value); + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/WorldState.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/WorldState.java index 10667e69c04..fc2c96e2c65 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/WorldState.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/WorldState.java @@ -115,5 +115,10 @@ public NavigableMap storageEntriesFrom( final Bytes32 startKeyHash, final int limit) { return accountState.storageEntriesFrom(startKeyHash, limit); } + + @Override + public UInt256 getTransientStorageValue(final UInt256 key) { + return accountState.getTransientStorageValue(key); + } } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/WrappedEvmAccount.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/WrappedEvmAccount.java index dcafaf1f85a..fe57c3c7671 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/WrappedEvmAccount.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/WrappedEvmAccount.java @@ -100,4 +100,9 @@ public NavigableMap storageEntriesFrom( final Bytes32 startKeyHash, final int limit) { return mutableAccount.storageEntriesFrom(startKeyHash, limit); } + + @Override + public UInt256 getTransientStorageValue(final UInt256 key) { + return mutableAccount.getTransientStorageValue(key); + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyAccount.java b/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyAccount.java index 9117096b6c8..fab6da7d938 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyAccount.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyAccount.java @@ -47,6 +47,7 @@ public class ToyAccount implements EvmAccount, MutableAccount { private Supplier codeHash = Suppliers.memoize(() -> code == null ? Hash.EMPTY : Hash.hash(code)); private final Map storage = new HashMap<>(); + private final Map transientStorage = new HashMap<>(); public ToyAccount(final Address address, final long nonce, final Wei balance) { this(null, address, nonce, balance, Bytes.EMPTY); @@ -148,10 +149,25 @@ public void setStorageValue(final UInt256 key, final UInt256 value) { @Override public void clearStorage() { storage.clear(); + transientStorage.clear(); } @Override public Map getUpdatedStorage() { return storage; } + + @Override + public UInt256 getTransientStorageValue(final UInt256 key) { + if (transientStorage.containsKey(key)) { + return transientStorage.get(key); + } else { + return UInt256.ZERO; + } + } + + @Override + public void setTransientStorageValue(final UInt256 key, final UInt256 value) { + transientStorage.put(key, value); + } }