diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/BlockUtils.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/BlockUtils.java index a02651a98fe..02b8adf56ad 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/BlockUtils.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/BlockUtils.java @@ -53,6 +53,7 @@ public static BlockHeader createBlockHeader( null, mixHash, new BigInteger(block.getNonceRaw().substring(2), 16).longValue(), + Hash.EMPTY, blockHeaderFunctions); } } diff --git a/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java b/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java index 894b54eaa53..4cab01f9537 100644 --- a/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java @@ -498,7 +498,8 @@ public Runner build() { .storageProvider(storageProvider) .p2pTLSConfiguration(p2pTLSConfiguration) .blockchain(context.getBlockchain()) - .forks(besuController.getGenesisConfigOptions().getForks()) + .blockNumberForks(besuController.getGenesisConfigOptions().getForkBlockNumbers()) + .timestampForks(besuController.getGenesisConfigOptions().getForkTimestamps()) .build(); final NetworkRunner networkRunner = diff --git a/besu/src/main/java/org/hyperledger/besu/cli/config/NetworkName.java b/besu/src/main/java/org/hyperledger/besu/cli/config/NetworkName.java index 550a5c073e0..67a9da0c881 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/config/NetworkName.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/config/NetworkName.java @@ -25,6 +25,7 @@ public enum NetworkName { ROPSTEN("/ropsten.json", BigInteger.valueOf(3)), SEPOLIA("/sepolia.json", BigInteger.valueOf(11155111)), GOERLI("/goerli.json", BigInteger.valueOf(5)), + SHANGHAI("/shanghai.json", BigInteger.valueOf(1337903)), SHANDONG("/shandong.json", BigInteger.valueOf(1337903)), KILN("/kiln.json", BigInteger.valueOf(1337802), false), DEV("/dev.json", BigInteger.valueOf(2018), false), diff --git a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java index 2482591f9d6..8e198b42680 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -609,7 +609,8 @@ protected EthProtocolManager createEthProtocolManager( mergePeerFilter, synchronizerConfiguration, scheduler, - genesisConfig.getForks()); + genesisConfig.getForkBlockNumbers(), + genesisConfig.getForkTimestamps()); } protected ProtocolContext createProtocolContext( diff --git a/besu/src/main/java/org/hyperledger/besu/controller/MergeBesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/MergeBesuControllerBuilder.java index 272fa3ada37..9ee618cccd0 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/MergeBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/MergeBesuControllerBuilder.java @@ -43,6 +43,7 @@ import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions; +import org.hyperledger.besu.ethereum.mainnet.TimestampSchedule; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.plugin.services.MetricsSystem; @@ -233,4 +234,9 @@ protected List createPeerValidators(final ProtocolSchedule protoc } return retval; } + + public TimestampSchedule createTimestampProtocolSchedule() { + return MergeProtocolSchedule.createTimestamp( + configOptionsSupplier.get(), privacyParameters, isRevertReasonEnabled); + } } diff --git a/besu/src/main/java/org/hyperledger/besu/controller/TransitionBesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/TransitionBesuControllerBuilder.java index 2465d11c498..ee1377060aa 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/TransitionBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/TransitionBesuControllerBuilder.java @@ -26,10 +26,12 @@ import org.hyperledger.besu.crypto.NodeKey; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ConsensusContext; +import org.hyperledger.besu.ethereum.ConsensusContextFactory; import org.hyperledger.besu.ethereum.GasLimitCalculator; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator; import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.Synchronizer; @@ -75,6 +77,7 @@ public class TransitionBesuControllerBuilder extends BesuControllerBuilder { private final MergeBesuControllerBuilder mergeBesuControllerBuilder; private static final Logger LOG = LoggerFactory.getLogger(TransitionBesuControllerBuilder.class); + private TransitionProtocolSchedule transitionProtocolSchedule; public TransitionBesuControllerBuilder( final BesuControllerBuilder preMergeBesuControllerBuilder, @@ -165,9 +168,26 @@ protected EthProtocolManager createEthProtocolManager( @Override protected ProtocolSchedule createProtocolSchedule() { - return new TransitionProtocolSchedule( - preMergeBesuControllerBuilder.createProtocolSchedule(), - mergeBesuControllerBuilder.createProtocolSchedule()); + transitionProtocolSchedule = + new TransitionProtocolSchedule( + preMergeBesuControllerBuilder.createProtocolSchedule(), + mergeBesuControllerBuilder.createProtocolSchedule(), + PostMergeContext.get(), + mergeBesuControllerBuilder.createTimestampProtocolSchedule()); + return transitionProtocolSchedule; + } + + @Override + protected ProtocolContext createProtocolContext( + final MutableBlockchain blockchain, + final WorldStateArchive worldStateArchive, + final ProtocolSchedule protocolSchedule, + final ConsensusContextFactory consensusContextFactory) { + final ProtocolContext protocolContext = + super.createProtocolContext( + blockchain, worldStateArchive, protocolSchedule, consensusContextFactory); + transitionProtocolSchedule.setProtocolContext(protocolContext); + return protocolContext; } @Override diff --git a/besu/src/test/java/org/hyperledger/besu/ForkIdsTest.java b/besu/src/test/java/org/hyperledger/besu/ForkIdsTest.java index 4e0ee8f9e1d..c6664d46f2d 100644 --- a/besu/src/test/java/org/hyperledger/besu/ForkIdsTest.java +++ b/besu/src/test/java/org/hyperledger/besu/ForkIdsTest.java @@ -26,10 +26,10 @@ import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.GenesisState; +import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.forkid.ForkId; import org.hyperledger.besu.ethereum.forkid.ForkIdManager; import org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSchedule; -import org.hyperledger.besu.ethereum.mainnet.MutableProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.evm.internal.EvmConfiguration; @@ -192,19 +192,23 @@ public void testForkId() { MainnetProtocolSchedule.fromConfig(configOptions, EvmConfiguration.DEFAULT); final GenesisState genesisState = GenesisState.fromConfig(genesisConfigFile, schedule); final Blockchain mockBlockchain = mock(Blockchain.class); + final BlockHeader mockBlockHeader = mock(BlockHeader.class); when(mockBlockchain.getGenesisBlock()).thenReturn(genesisState.getBlock()); final AtomicLong blockNumber = new AtomicLong(); - when(mockBlockchain.getChainHeadBlockNumber()).thenAnswer(o -> blockNumber.get()); + when(mockBlockchain.getChainHeadHeader()).thenReturn(mockBlockHeader); + when(mockBlockHeader.getNumber()).thenAnswer(o -> blockNumber.get()); final ForkIdManager forkIdManager = - new ForkIdManager(mockBlockchain, genesisConfigFile.getForks(), false); + new ForkIdManager( + mockBlockchain, + genesisConfigFile.getForkBlockNumbers(), + genesisConfigFile.getForkTimestamps(), + false); final var actualForkIds = - Streams.concat( - ((MutableProtocolSchedule) schedule).streamMilestoneBlocks(), - Stream.of(Long.MAX_VALUE)) + Streams.concat(schedule.streamMilestoneBlocks(), Stream.of(Long.MAX_VALUE)) .map( block -> { blockNumber.set(block); diff --git a/besu/src/test/java/org/hyperledger/besu/RunnerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/RunnerBuilderTest.java index 8a1b486b7c7..211c466724c 100644 --- a/besu/src/test/java/org/hyperledger/besu/RunnerBuilderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/RunnerBuilderTest.java @@ -136,7 +136,8 @@ public void setup() { when(besuController.getMiningCoordinator()).thenReturn(mock(MiningCoordinator.class)); when(besuController.getMiningCoordinator()).thenReturn(mock(MergeMiningCoordinator.class)); final GenesisConfigOptions genesisConfigOptions = mock(GenesisConfigOptions.class); - when(genesisConfigOptions.getForks()).thenReturn(Collections.emptyList()); + when(genesisConfigOptions.getForkBlockNumbers()).thenReturn(Collections.emptyList()); + when(genesisConfigOptions.getForkTimestamps()).thenReturn(Collections.emptyList()); when(besuController.getGenesisConfigOptions()).thenReturn(genesisConfigOptions); } 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 dea9dcd975f..acb75813216 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java @@ -30,6 +30,7 @@ import static org.hyperledger.besu.cli.config.NetworkName.ROPSTEN; import static org.hyperledger.besu.cli.config.NetworkName.SEPOLIA; import static org.hyperledger.besu.cli.config.NetworkName.SHANDONG; +import static org.hyperledger.besu.cli.config.NetworkName.SHANGHAI; import static org.hyperledger.besu.cli.util.CommandLineUtils.DEPENDENCY_WARNING_MSG; import static org.hyperledger.besu.cli.util.CommandLineUtils.DEPRECATION_WARNING_MSG; import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.ENGINE; @@ -1044,6 +1045,23 @@ public void testGenesisPathRinkebyEthConfig() { assertThat(config.getNetworkId()).isEqualTo(BigInteger.valueOf(4)); } + @Ignore + @Test + public void testGenesisPathShanghaiEthConfig() { + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); + + parseCommand("--network", "shanghai"); + + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); + verify(mockControllerBuilder).build(); + + final EthNetworkConfig config = networkArg.getValue(); + assertThat(config.getBootNodes()).isEqualTo(SHANDONG_BOOTSTRAP_NODES); + assertThat(config.getDnsDiscoveryUrl()).isNull(); + assertThat(config.getNetworkId()).isEqualTo(BigInteger.valueOf(1337903)); + } + @Test public void testGenesisPathShandongEthConfig() { final ArgumentCaptor networkArg = @@ -4009,6 +4027,24 @@ public void sepoliaValuesAreUsed() { verify(mockLogger, never()).warn(contains("Sepolia is deprecated and will be shutdown")); } + @Test + public void shanghaiValuesAreUsed() { + parseCommand("--network", "shanghai"); + + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); + + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); + verify(mockControllerBuilder).build(); + + assertThat(networkArg.getValue()).isEqualTo(EthNetworkConfig.getNetworkConfig(SHANGHAI)); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + + verify(mockLogger, never()).warn(contains("Shanghai is deprecated and will be shutdown")); + } + @Test public void shandongValuesAreUsed() { parseCommand("--network", "shandong"); @@ -4090,6 +4126,10 @@ public void ropstenValuesCanBeOverridden() throws Exception { networkValuesCanBeOverridden("ropsten"); } + public void shanghaiValuesCanBeOverridden() throws Exception { + networkValuesCanBeOverridden("shanghai"); + } + public void shandongValuesCanBeOverridden() throws Exception { networkValuesCanBeOverridden("shandong"); } diff --git a/besu/src/test/java/org/hyperledger/besu/controller/BesuControllerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/controller/BesuControllerBuilderTest.java index 69c8814bfee..e320ca2688e 100644 --- a/besu/src/test/java/org/hyperledger/besu/controller/BesuControllerBuilderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/controller/BesuControllerBuilderTest.java @@ -100,6 +100,7 @@ public void setup() { when(genesisConfigFile.getMixHash()).thenReturn(Hash.ZERO.toHexString()); when(genesisConfigFile.getNonce()).thenReturn(Long.toHexString(1)); when(genesisConfigFile.getConfigOptions(any())).thenReturn(genesisConfigOptions); + when(genesisConfigFile.getConfigOptions()).thenReturn(genesisConfigOptions); when(genesisConfigOptions.getThanosBlockNumber()).thenReturn(OptionalLong.empty()); when(genesisConfigOptions.getEthashConfigOptions()).thenReturn(ethashConfigOptions); when(genesisConfigOptions.getCheckpointOptions()).thenReturn(checkpointConfigOptions); diff --git a/besu/src/test/java/org/hyperledger/besu/controller/MergeBesuControllerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/controller/MergeBesuControllerBuilderTest.java index b071f6a7fa5..5a29193e4de 100644 --- a/besu/src/test/java/org/hyperledger/besu/controller/MergeBesuControllerBuilderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/controller/MergeBesuControllerBuilderTest.java @@ -110,6 +110,7 @@ public void setup() { when(genesisConfigFile.getMixHash()).thenReturn(Hash.ZERO.toHexString()); when(genesisConfigFile.getNonce()).thenReturn(Long.toHexString(1)); when(genesisConfigFile.getConfigOptions(any())).thenReturn(genesisConfigOptions); + when(genesisConfigFile.getConfigOptions()).thenReturn(genesisConfigOptions); when(genesisConfigOptions.getCheckpointOptions()).thenReturn(checkpointConfigOptions); when(genesisConfigOptions.getTerminalTotalDifficulty()) .thenReturn((Optional.of(UInt256.valueOf(100L)))); diff --git a/besu/src/test/java/org/hyperledger/besu/controller/QbftBesuControllerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/controller/QbftBesuControllerBuilderTest.java index 345b9bd94ec..a6ed26159cf 100644 --- a/besu/src/test/java/org/hyperledger/besu/controller/QbftBesuControllerBuilderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/controller/QbftBesuControllerBuilderTest.java @@ -100,6 +100,7 @@ public void setup() { when(genesisConfigFile.getMixHash()).thenReturn(Hash.ZERO.toHexString()); when(genesisConfigFile.getNonce()).thenReturn(Long.toHexString(1)); when(genesisConfigFile.getConfigOptions(any())).thenReturn(genesisConfigOptions); + when(genesisConfigFile.getConfigOptions()).thenReturn(genesisConfigOptions); when(genesisConfigOptions.getCheckpointOptions()).thenReturn(checkpointConfigOptions); when(storageProvider.createBlockchainStorage(any())) .thenReturn( diff --git a/besu/src/test/java/org/hyperledger/besu/controller/TransitionControllerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/controller/TransitionControllerBuilderTest.java index 48cf9954c0d..02a37fff381 100644 --- a/besu/src/test/java/org/hyperledger/besu/controller/TransitionControllerBuilderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/controller/TransitionControllerBuilderTest.java @@ -44,6 +44,7 @@ import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderValidator; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.mainnet.TimestampSchedule; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; @@ -66,6 +67,7 @@ public class TransitionControllerBuilderTest { @Mock ProtocolSchedule preMergeProtocolSchedule; @Mock ProtocolSchedule postMergeProtocolSchedule; + @Mock TimestampSchedule timestampSchedule; @Mock ProtocolContext protocolContext; @Mock MutableBlockchain mockBlockchain; @Mock TransactionPool transactionPool; @@ -86,7 +88,11 @@ public void setup() { transitionProtocolSchedule = spy( new TransitionProtocolSchedule( - preMergeProtocolSchedule, postMergeProtocolSchedule, mergeContext)); + preMergeProtocolSchedule, + postMergeProtocolSchedule, + mergeContext, + timestampSchedule)); + transitionProtocolSchedule.setProtocolContext(protocolContext); cliqueBuilder.nodeKey(NodeKeyUtils.generate()); postMergeBuilder.storageProvider(storageProvider); when(protocolContext.getBlockchain()).thenReturn(mockBlockchain); @@ -126,7 +132,7 @@ public void assertPreMergeScheduleForNotPostMerge() { when(mergeContext.isPostMerge()).thenReturn(Boolean.FALSE); when(mergeContext.getFinalized()).thenReturn(Optional.empty()); when(preMergeProtocolSchedule.getByBlockNumber(anyLong())).thenReturn(preMergeProtocolSpec); - assertThat(transitionProtocolSchedule.getByBlockHeader(protocolContext, mockBlock)) + assertThat(transitionProtocolSchedule.getByBlockHeader(mockBlock)) .isEqualTo(preMergeProtocolSpec); } @@ -136,7 +142,7 @@ public void assertPostMergeScheduleForAnyBlockWhenPostMergeAndFinalized() { var postMergeProtocolSpec = mock(ProtocolSpec.class); when(mergeContext.getFinalized()).thenReturn(Optional.of(mockBlock)); when(postMergeProtocolSchedule.getByBlockNumber(anyLong())).thenReturn(postMergeProtocolSpec); - assertThat(transitionProtocolSchedule.getByBlockHeader(protocolContext, mockBlock)) + assertThat(transitionProtocolSchedule.getByBlockHeader(mockBlock)) .isEqualTo(postMergeProtocolSpec); } @@ -159,7 +165,7 @@ public void assertPreMergeScheduleForBelowTerminalBlockWhenPostMergeIfNotFinaliz .thenReturn(Optional.of(Difficulty.of(1335L))); when(preMergeProtocolSchedule.getByBlockNumber(anyLong())).thenReturn(preMergeProtocolSpec); - assertThat(transitionProtocolSchedule.getByBlockHeader(protocolContext, mockBlock)) + assertThat(transitionProtocolSchedule.getByBlockHeader(mockBlock)) .isEqualTo(preMergeProtocolSpec); } @@ -182,7 +188,7 @@ public void assertPostMergeScheduleForPostMergeExactlyAtTerminalDifficultyIfNotF .thenReturn(Optional.of(Difficulty.of(1337L))); when(postMergeProtocolSchedule.getByBlockNumber(anyLong())).thenReturn(postMergeProtocolSpec); - assertThat(transitionProtocolSchedule.getByBlockHeader(protocolContext, mockBlock)) + assertThat(transitionProtocolSchedule.getByBlockHeader(mockBlock)) .isEqualTo(postMergeProtocolSpec); } diff --git a/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java b/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java index 6eac33e3278..4df45c00194 100644 --- a/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java +++ b/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java @@ -192,7 +192,8 @@ private void setSyncTarget() { syncState.setSyncTarget( mock(EthPeer.class), new org.hyperledger.besu.ethereum.core.BlockHeader( - null, null, null, null, null, null, null, null, 1, 1, 1, 1, null, null, null, 1, null)); + null, null, null, null, null, null, null, null, 1, 1, 1, 1, null, null, null, 1, null, + null)); } private void clearSyncTarget() { @@ -484,7 +485,8 @@ public void logEventDoesNotFireAfterUnsubscribe() { } private Block generateBlock() { - final BlockBody body = new BlockBody(Collections.emptyList(), Collections.emptyList()); + final BlockBody body = + new BlockBody(Collections.emptyList(), Collections.emptyList(), Optional.empty()); return new Block(new BlockHeaderTestFixture().buildHeader(), body); } diff --git a/besu/src/test/resources/log4j2.xml b/besu/src/test/resources/log4j2.xml new file mode 100644 index 00000000000..ec7a9d96ece --- /dev/null +++ b/besu/src/test/resources/log4j2.xml @@ -0,0 +1,89 @@ + + + + INFO + + + + + + %X{test} | %X{node} | %d{HH:mm:ss.SSS} | %t | %-5level | %c{1} | %msg%n + + + + + + + + + + + + + + %X{node} | %d{HH:mm:ss.SSS} | %t | %-5level | %c{1} | %msg%n + + + + + + + + + %X{node} | %d{HH:mm:ss.SSS} | %t | %-5level | %c{1} | %msg%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build.gradle b/build.gradle index 50b8055550c..e8c77c7f7b4 100644 --- a/build.gradle +++ b/build.gradle @@ -639,16 +639,21 @@ task distDocker { def dockerBuildDir = "build/docker-besu/" doLast { - for (def variant in dockerVariants) { + for (def jvmVariant in dockerVariants) { copy { - from file("${projectDir}/docker/${variant}/Dockerfile") + from file("${projectDir}/docker/${jvmVariant}/Dockerfile") into(dockerBuildDir) } exec { - def image = "${dockerImageName}:${dockerBuildVersion}-${variant}" + def image = "${dockerImageName}:${dockerBuildVersion}-${jvmVariant}" + def dockerPlatform = "" + if (project.hasProperty('docker-platform')){ + dockerPlatform = "--platform ${project.getProperty('docker-platform')}" + println "Building for platform ${project.getProperty('docker-platform')}" + } executable "sh" workingDir dockerBuildDir - args "-c", "docker build --build-arg BUILD_DATE=${buildTime()} --build-arg VERSION=${dockerBuildVersion} --build-arg VCS_REF=${getCheckedOutGitCommitHash()} -t ${image} ." + args "-c", "docker build ${dockerPlatform} --build-arg BUILD_DATE=${buildTime()} --build-arg VERSION=${dockerBuildVersion} --build-arg VCS_REF=${getCheckedOutGitCommitHash()} -t ${image} ." } } // tag the "default" (which is the variant in the zero position) diff --git a/config/src/main/java/org/hyperledger/besu/config/GenesisConfigFile.java b/config/src/main/java/org/hyperledger/besu/config/GenesisConfigFile.java index 8bc187b5678..0f66e2fef47 100644 --- a/config/src/main/java/org/hyperledger/besu/config/GenesisConfigFile.java +++ b/config/src/main/java/org/hyperledger/besu/config/GenesisConfigFile.java @@ -199,7 +199,11 @@ private long parseLong(final String name, final String value) { } } - public List getForks() { - return getConfigOptions().getForks(); + public List getForkBlockNumbers() { + return getConfigOptions().getForkBlockNumbers(); + } + + public List getForkTimestamps() { + return getConfigOptions().getForkTimestamps(); } } 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 827004851cb..d5932f84867 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,10 @@ default boolean isConsensusMigration() { OptionalLong getMergeNetSplitBlockNumber(); + OptionalLong getShanghaiTime(); + + OptionalLong getCancunTime(); + OptionalLong getShandongBlockNumber(); Optional getBaseFeePerGas(); @@ -100,7 +104,9 @@ default boolean isConsensusMigration() { Optional getTerminalBlockHash(); - List getForks(); + List getForkBlockNumbers(); + + List getForkTimestamps(); /** * Block number for the Dao Fork, this value is used to tell node to connect with peer that did 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 b8970d31de8..fa1f43ac2d2 100644 --- a/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java @@ -278,6 +278,16 @@ public OptionalLong getMergeNetSplitBlockNumber() { return getOptionalLong("mergenetsplitblock"); } + @Override + public OptionalLong getShanghaiTime() { + return getOptionalLong("shanghaitime"); + } + + @Override + public OptionalLong getCancunTime() { + return getOptionalLong("cancuntime"); + } + @Override public OptionalLong getShandongBlockNumber() { return getOptionalLong("shandongblock"); @@ -433,6 +443,8 @@ public Map asMap() { getArrowGlacierBlockNumber().ifPresent(l -> builder.put("arrowGlacierBlock", l)); getGrayGlacierBlockNumber().ifPresent(l -> builder.put("grayGlacierBlock", l)); getMergeNetSplitBlockNumber().ifPresent(l -> builder.put("mergeNetSplitBlock", l)); + getShanghaiTime().ifPresent(l -> builder.put("shanghaiTime", l)); + getCancunTime().ifPresent(l -> builder.put("cancunTime", l)); getShandongBlockNumber().ifPresent(l -> builder.put("shandongBlock", l)); getTerminalBlockNumber().ifPresent(l -> builder.put("terminalBlockNumber", l)); getTerminalBlockHash().ifPresent(h -> builder.put("terminalBlockHash", h.toHexString())); @@ -540,7 +552,7 @@ private Optional getOptionalHash(final String key) { } @Override - public List getForks() { + public List getForkBlockNumbers() { Stream forkBlockNumbers = Stream.of( getHomesteadBlockNumber(), @@ -578,4 +590,17 @@ public List getForks() { .sorted() .collect(Collectors.toList()); } + + @Override + public List getForkTimestamps() { + Stream forkBlockTimestamps = Stream.of(getShanghaiTime(), getCancunTime()); + // when adding forks add an entry to ${REPO_ROOT}/config/src/test/resources/all_forks.json + + return forkBlockTimestamps + .filter(OptionalLong::isPresent) + .map(OptionalLong::getAsLong) + .distinct() + .sorted() + .collect(Collectors.toList()); + } } 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 54eb1becc67..bb2b59f060a 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,8 @@ public class StubGenesisConfigOptions implements GenesisConfigOptions { private OptionalLong arrowGlacierBlockNumber = OptionalLong.empty(); private OptionalLong grayGlacierBlockNumber = OptionalLong.empty(); private OptionalLong mergeNetSplitBlockNumber = OptionalLong.empty(); + private OptionalLong shanghaiTime = OptionalLong.empty(); + private OptionalLong cancunTime = OptionalLong.empty(); private OptionalLong shandongBlockNumber = OptionalLong.empty(); private OptionalLong terminalBlockNumber = OptionalLong.empty(); private Optional terminalBlockHash = Optional.empty(); @@ -217,6 +219,16 @@ public OptionalLong getMergeNetSplitBlockNumber() { return mergeNetSplitBlockNumber; } + @Override + public OptionalLong getShanghaiTime() { + return shanghaiTime; + } + + @Override + public OptionalLong getCancunTime() { + return cancunTime; + } + @Override public OptionalLong getShandongBlockNumber() { return shandongBlockNumber; @@ -342,6 +354,8 @@ public Map asMap() { getArrowGlacierBlockNumber().ifPresent(l -> builder.put("arrowGlacierBlock", l)); getGrayGlacierBlockNumber().ifPresent(l -> builder.put("grayGlacierBlock", l)); getMergeNetSplitBlockNumber().ifPresent(l -> builder.put("mergeNetSplitBlock", l)); + getShanghaiTime().ifPresent(l -> builder.put("shanghaiTime", l)); + getCancunTime().ifPresent(l -> builder.put("cancunTime", l)); getShandongBlockNumber().ifPresent(l -> builder.put("shandongBlock", l)); getTerminalBlockNumber().ifPresent(l -> builder.put("terminalBlockNumber", l)); getTerminalBlockHash().ifPresent(h -> builder.put("terminalBlockHash", h)); @@ -412,7 +426,12 @@ public boolean isZeroBaseFee() { } @Override - public List getForks() { + public List getForkBlockNumbers() { + return Collections.emptyList(); + } + + @Override + public List getForkTimestamps() { return Collections.emptyList(); } @@ -486,6 +505,16 @@ public StubGenesisConfigOptions mergeNetSplitBlock(final long blockNumber) { return this; } + public StubGenesisConfigOptions shanghaiTime(final long timestamp) { + shanghaiTime = OptionalLong.of(timestamp); + return this; + } + + public StubGenesisConfigOptions cancunTime(final long timestamp) { + cancunTime = OptionalLong.of(timestamp); + return this; + } + public StubGenesisConfigOptions shandongBlock(final long blockNumber) { shandongBlockNumber = OptionalLong.of(blockNumber); return this; diff --git a/config/src/main/resources/shanghai.json b/config/src/main/resources/shanghai.json new file mode 100644 index 00000000000..d4493434acf --- /dev/null +++ b/config/src/main/resources/shanghai.json @@ -0,0 +1,869 @@ +{ + "config": { + "chainId": 1337907, + "shanghaiBlock": 0, + "terminalTotalDifficulty": 0, + "ethash": {}, + "discovery": { + "bootnodes": [ + "enode://11e43515d6258ab2bd814b25a50c911c155a46a27e9be8ce6ea68e293ec13aa4cd6740418baf9abf1e79ba9252c497d66aa4a293c94ef8168d0e7c211ef73690@46.101.126.45:30303", + "enode://95b683a66aba396551bafff688644fc6dc1bada2de78491f89b268ebdcf3d88dfc9942a9f2833ecc3887d49dfb4e96851c2f5eb0adde41847cc356f15ac4ac67@178.128.206.76:30303", + "enode://bcd3eeabca8ff3a1c3b19384e064cc79fda547939324e0699d030041a8960a1d68f1e19141d19305e79675be50434521d024352a5eae00da8ace7933abf20fdf@142.93.160.7:30303", + "enode://6a65c7e62360e1fcc98c88cf8dd8e9492d2e95372f5d7b742b73ab8f82e849bae196bd18b79bc9502b204cef7bf64b589147700368d877c32a76f9c4fd7dd941@104.248.21.4:30303", + "enode://2ae5ef4b4c338e4ee15a347aba8e60bcb451ca19d26f8d67e731bb5f2972ad6ce867ec85f9942a5efe349470106b21c49a9aeb5b00b91063e35f1c1643c0930c@104.248.251.20:30303", + "enode://85ecd8fcab723a33ee68e5f8530c9d264bd601899dc74af7f020bc2f8e0d0ad8ec5c5324364f127c77ddab607bc16e1c5fbae2c675b6d94a79a24222e7e4766e@164.92.174.56:30303", + "enode://24951d3b76f20aed18e32096941d572b98518c9e4dace6b7d0b1f34292dfc5ee7d295453b955be4d2967c6ce1948c11513cd70d76dbee8d3034ddb40d63f5517@142.93.173.170:30303", + "enode://73e2396ac78c462287edd22957096890ca1995a4995c51a1fe24accbe931209fb0fc5359fc1043a0edad3fc53ba64fba5262255c70a03e5fca86f924c261ad4f@178.128.203.243:30303" + ] + } + }, + "baseFeePerGas": "1000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "difficulty": "0x01", + "extraData": "", + "gasLimit": "0x400000", + "nonce": "0x1234", + "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": "1666736008", + "alloc": { + "0x0000000000000000000000000000000000000000": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000001": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000002": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000003": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000004": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000005": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000006": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000007": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000008": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000009": { + "balance": "1" + }, + "0x000000000000000000000000000000000000000a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000000b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000000c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000000d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000000e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000000f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000010": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000011": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000012": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000013": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000014": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000015": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000016": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000017": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000018": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000019": { + "balance": "1" + }, + "0x000000000000000000000000000000000000001a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000001b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000001c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000001d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000001e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000001f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000020": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000021": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000022": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000023": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000024": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000025": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000026": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000027": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000028": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000029": { + "balance": "1" + }, + "0x000000000000000000000000000000000000002a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000002b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000002c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000002d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000002e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000002f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000030": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000031": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000032": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000033": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000034": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000035": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000036": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000037": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000038": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000039": { + "balance": "1" + }, + "0x000000000000000000000000000000000000003a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000003b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000003c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000003d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000003e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000003f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000040": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000041": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000042": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000043": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000044": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000045": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000046": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000047": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000048": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000049": { + "balance": "1" + }, + "0x000000000000000000000000000000000000004a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000004b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000004c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000004d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000004e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000004f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000050": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000051": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000052": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000053": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000054": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000055": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000056": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000057": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000058": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000059": { + "balance": "1" + }, + "0x000000000000000000000000000000000000005a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000005b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000005c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000005d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000005e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000005f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000060": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000061": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000062": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000063": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000064": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000065": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000066": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000067": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000068": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000069": { + "balance": "1" + }, + "0x000000000000000000000000000000000000006a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000006b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000006c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000006d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000006e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000006f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000070": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000071": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000072": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000073": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000074": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000075": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000076": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000077": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000078": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000079": { + "balance": "1" + }, + "0x000000000000000000000000000000000000007a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000007b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000007c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000007d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000007e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000007f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000080": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000081": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000082": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000083": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000084": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000085": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000086": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000087": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000088": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000089": { + "balance": "1" + }, + "0x000000000000000000000000000000000000008a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000008b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000008c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000008d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000008e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000008f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000090": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000091": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000092": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000093": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000094": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000095": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000096": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000097": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000098": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000099": { + "balance": "1" + }, + "0x000000000000000000000000000000000000009a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000009b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000009c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000009d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000009e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000009f": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a0": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a1": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a2": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a3": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a4": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a5": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a6": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a7": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a8": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a9": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000aa": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ab": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ac": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ad": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ae": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000af": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b0": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b1": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b2": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b3": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b4": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b5": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b6": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b7": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b8": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b9": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ba": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000bb": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000bc": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000bd": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000be": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000bf": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c0": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c1": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c2": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c3": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c4": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c5": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c6": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c7": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c8": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c9": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ca": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000cb": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000cc": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000cd": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ce": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000cf": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d0": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d1": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d2": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d3": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d4": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d5": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d6": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d7": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d8": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d9": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000da": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000db": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000dc": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000dd": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000de": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000df": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e0": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e1": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e2": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e3": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e4": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e5": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e6": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e7": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e8": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e9": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ea": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000eb": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ec": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ed": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ee": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ef": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f0": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f1": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f2": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f3": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f4": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f5": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f6": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f7": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f8": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f9": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000fa": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000fb": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000fc": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000fd": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000fe": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ff": { + "balance": "1" + }, + "0x4242424242424242424242424242424242424242": { + "balance": "0", + "code": "0x60806040526004361061003f5760003560e01c806301ffc9a71461004457806322895118146100a4578063621fd130146101ba578063c5f2892f14610244575b600080fd5b34801561005057600080fd5b506100906004803603602081101561006757600080fd5b50357fffffffff000000000000000000000000000000000000000000000000000000001661026b565b604080519115158252519081900360200190f35b6101b8600480360360808110156100ba57600080fd5b8101906020810181356401000000008111156100d557600080fd5b8201836020820111156100e757600080fd5b8035906020019184600183028401116401000000008311171561010957600080fd5b91939092909160208101903564010000000081111561012757600080fd5b82018360208201111561013957600080fd5b8035906020019184600183028401116401000000008311171561015b57600080fd5b91939092909160208101903564010000000081111561017957600080fd5b82018360208201111561018b57600080fd5b803590602001918460018302840111640100000000831117156101ad57600080fd5b919350915035610304565b005b3480156101c657600080fd5b506101cf6110b5565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102095781810151838201526020016101f1565b50505050905090810190601f1680156102365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561025057600080fd5b506102596110c7565b60408051918252519081900360200190f35b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a70000000000000000000000000000000000000000000000000000000014806102fe57507fffffffff0000000000000000000000000000000000000000000000000000000082167f8564090700000000000000000000000000000000000000000000000000000000145b92915050565b6030861461035d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118056026913960400191505060405180910390fd5b602084146103b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603681526020018061179c6036913960400191505060405180910390fd5b6060821461040f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806118786029913960400191505060405180910390fd5b670de0b6b3a7640000341015610470576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118526026913960400191505060405180910390fd5b633b9aca003406156104cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260338152602001806117d26033913960400191505060405180910390fd5b633b9aca00340467ffffffffffffffff811115610535576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602781526020018061182b6027913960400191505060405180910390fd5b6060610540826114ba565b90507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c589898989858a8a6105756020546114ba565b6040805160a0808252810189905290819060208201908201606083016080840160c085018e8e80828437600083820152601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690910187810386528c815260200190508c8c808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690920188810386528c5181528c51602091820193918e019250908190849084905b83811015610648578181015183820152602001610630565b50505050905090810190601f1680156106755780820380516001836020036101000a031916815260200191505b5086810383528881526020018989808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169092018881038452895181528951602091820193918b019250908190849084905b838110156106ef5781810151838201526020016106d7565b50505050905090810190601f16801561071c5780820380516001836020036101000a031916815260200191505b509d505050505050505050505050505060405180910390a1600060028a8a600060801b604051602001808484808284377fffffffffffffffffffffffffffffffff0000000000000000000000000000000090941691909301908152604080517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0818403018152601090920190819052815191955093508392506020850191508083835b602083106107fc57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016107bf565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610859573d6000803e3d6000fd5b5050506040513d602081101561086e57600080fd5b5051905060006002806108846040848a8c6116fe565b6040516020018083838082843780830192505050925050506040516020818303038152906040526040518082805190602001908083835b602083106108f857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016108bb565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610955573d6000803e3d6000fd5b5050506040513d602081101561096a57600080fd5b5051600261097b896040818d6116fe565b60405160009060200180848480828437919091019283525050604080518083038152602092830191829052805190945090925082918401908083835b602083106109f457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016109b7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610a51573d6000803e3d6000fd5b5050506040513d6020811015610a6657600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610ada57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610a9d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610b37573d6000803e3d6000fd5b5050506040513d6020811015610b4c57600080fd5b50516040805160208101858152929350600092600292839287928f928f92018383808284378083019250505093505050506040516020818303038152906040526040518082805190602001908083835b60208310610bd957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610b9c565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610c36573d6000803e3d6000fd5b5050506040513d6020811015610c4b57600080fd5b50516040518651600291889160009188916020918201918291908601908083835b60208310610ca957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610c6c565b6001836020036101000a0380198251168184511680821785525050505050509050018367ffffffffffffffff191667ffffffffffffffff1916815260180182815260200193505050506040516020818303038152906040526040518082805190602001908083835b60208310610d4e57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610d11565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610dab573d6000803e3d6000fd5b5050506040513d6020811015610dc057600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610e3457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610df7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610e91573d6000803e3d6000fd5b5050506040513d6020811015610ea657600080fd5b50519050858114610f02576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260548152602001806117486054913960600191505060405180910390fd5b60205463ffffffff11610f60576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806117276021913960400191505060405180910390fd5b602080546001019081905560005b60208110156110a9578160011660011415610fa0578260008260208110610f9157fe5b0155506110ac95505050505050565b600260008260208110610faf57fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061102557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610fe8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015611082573d6000803e3d6000fd5b5050506040513d602081101561109757600080fd5b50519250600282049150600101610f6e565b50fe5b50505050505050565b60606110c26020546114ba565b905090565b6020546000908190815b60208110156112f05781600116600114156111e6576002600082602081106110f557fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061116b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161112e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156111c8573d6000803e3d6000fd5b5050506040513d60208110156111dd57600080fd5b505192506112e2565b600283602183602081106111f657fe5b015460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061126b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161122e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156112c8573d6000803e3d6000fd5b5050506040513d60208110156112dd57600080fd5b505192505b6002820491506001016110d1565b506002826112ff6020546114ba565b600060401b6040516020018084815260200183805190602001908083835b6020831061135a57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161131d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790527fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000095909516920191825250604080518083037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8018152601890920190819052815191955093508392850191508083835b6020831061143f57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101611402565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa15801561149c573d6000803e3d6000fd5b5050506040513d60208110156114b157600080fd5b50519250505090565b60408051600880825281830190925260609160208201818036833701905050905060c082901b8060071a60f81b826000815181106114f457fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060061a60f81b8260018151811061153757fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060051a60f81b8260028151811061157a57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060041a60f81b826003815181106115bd57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060031a60f81b8260048151811061160057fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060021a60f81b8260058151811061164357fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060011a60f81b8260068151811061168657fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060001a60f81b826007815181106116c957fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535050919050565b6000808585111561170d578182fd5b83861115611719578182fd5b505082019391909203915056fe4465706f736974436f6e74726163743a206d65726b6c6520747265652066756c6c4465706f736974436f6e74726163743a207265636f6e7374727563746564204465706f7369744461746120646f6573206e6f74206d6174636820737570706c696564206465706f7369745f646174615f726f6f744465706f736974436f6e74726163743a20696e76616c6964207769746864726177616c5f63726564656e7469616c73206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c7565206e6f74206d756c7469706c65206f6620677765694465706f736974436f6e74726163743a20696e76616c6964207075626b6579206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f20686967684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f206c6f774465706f736974436f6e74726163743a20696e76616c6964207369676e6174757265206c656e677468a26469706673582212201dd26f37a621703009abf16e77e69c93dc50c79db7f6cc37543e3e0e3decdc9764736f6c634300060b0033", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000022": "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b", + "0x0000000000000000000000000000000000000000000000000000000000000023": "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", + "0x0000000000000000000000000000000000000000000000000000000000000024": "0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c", + "0x0000000000000000000000000000000000000000000000000000000000000025": "0x536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c", + "0x0000000000000000000000000000000000000000000000000000000000000026": "0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30", + "0x0000000000000000000000000000000000000000000000000000000000000027": "0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1", + "0x0000000000000000000000000000000000000000000000000000000000000028": "0x87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c", + "0x0000000000000000000000000000000000000000000000000000000000000029": "0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193", + "0x000000000000000000000000000000000000000000000000000000000000002a": "0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1", + "0x000000000000000000000000000000000000000000000000000000000000002b": "0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b", + "0x000000000000000000000000000000000000000000000000000000000000002c": "0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220", + "0x000000000000000000000000000000000000000000000000000000000000002d": "0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f", + "0x000000000000000000000000000000000000000000000000000000000000002e": "0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e", + "0x000000000000000000000000000000000000000000000000000000000000002f": "0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784", + "0x0000000000000000000000000000000000000000000000000000000000000030": "0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb", + "0x0000000000000000000000000000000000000000000000000000000000000031": "0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb", + "0x0000000000000000000000000000000000000000000000000000000000000032": "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab", + "0x0000000000000000000000000000000000000000000000000000000000000033": "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4", + "0x0000000000000000000000000000000000000000000000000000000000000034": "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f", + "0x0000000000000000000000000000000000000000000000000000000000000035": "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", + "0x0000000000000000000000000000000000000000000000000000000000000036": "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", + "0x0000000000000000000000000000000000000000000000000000000000000037": "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", + "0x0000000000000000000000000000000000000000000000000000000000000038": "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", + "0x0000000000000000000000000000000000000000000000000000000000000039": "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", + "0x000000000000000000000000000000000000000000000000000000000000003a": "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", + "0x000000000000000000000000000000000000000000000000000000000000003b": "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", + "0x000000000000000000000000000000000000000000000000000000000000003c": "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", + "0x000000000000000000000000000000000000000000000000000000000000003d": "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", + "0x000000000000000000000000000000000000000000000000000000000000003e": "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", + "0x000000000000000000000000000000000000000000000000000000000000003f": "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", + "0x0000000000000000000000000000000000000000000000000000000000000040": "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7" + } + }, + "0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134": { + "balance": "10000000000000000000000000" + }, + "0x2cA5F489CC1Fd1CEC24747B64E8dE0F4A6A850E1": { + "balance": "10000000000000000000000000" + }, + "0x7203bd333a874D9d329050ecE393820fCD501eaA": { + "balance": "10000000000000000000000000" + }, + "0xA51918aA40D78Ff8be939bf0E8404252875c6aDF": { + "balance": "10000000000000000000000000" + }, + "0xAA81078e6b2121dd7A846690DFdD6b10d7658d8B": { + "balance": "10000000000000000000000000" + }, + "0xFA2d31D8f21c1D1633E9BEB641dF77D21D63ccDd": { + "balance": "10000000000000000000000000" + }, + "0xf751C9c6d60614226fE57D2cAD6e10C856a2ddA3": { + "balance": "10000000000000000000000000" + }, + "0x9cD16887f6A808AEaa65D3c840f059EeA4ca1319": { + "balance": "10000000000000000000000000" + }, + "0x2E07043584F11BFF0AC39c927665DF6c6ebaffFB": { + "balance": "10000000000000000000000000" + }, + "0x806ce45534bb07a2CAd3a84c53611a2b3DdE316A": { + "balance": "10000000000000000000000000" + }, + "0x97C9B168C5E14d5D369B6D88E9776E5B7b11dcC1": { + "balance": "10000000000000000000000000" + } + } +} \ No newline at end of file diff --git a/config/src/test/java/org/hyperledger/besu/config/GenesisConfigFileTest.java b/config/src/test/java/org/hyperledger/besu/config/GenesisConfigFileTest.java index 31b0098f2eb..bdaeeb5100b 100644 --- a/config/src/test/java/org/hyperledger/besu/config/GenesisConfigFileTest.java +++ b/config/src/test/java/org/hyperledger/besu/config/GenesisConfigFileTest.java @@ -252,7 +252,7 @@ public void shouldFindMergeNetSplitForkAndAlias() { GenesisConfigFile mergeNetSplitGenesis = GenesisConfigFile.fromConfig( "{\"config\":{\"mergeNetsplitBlock\":11},\"baseFeePerGas\":\"0xa\"}"); - assertThat(mergeNetSplitGenesis.getForks()).hasSize(1); + assertThat(mergeNetSplitGenesis.getForkBlockNumbers()).hasSize(1); assertThat(mergeNetSplitGenesis.getConfigOptions().getMergeNetSplitBlockNumber()).isPresent(); assertThat(mergeNetSplitGenesis.getConfigOptions().getMergeNetSplitBlockNumber().getAsLong()) .isEqualTo(11L); @@ -260,7 +260,7 @@ public void shouldFindMergeNetSplitForkAndAlias() { // assert empty if not present: GenesisConfigFile londonGenesis = GenesisConfigFile.fromConfig("{\"config\":{\"londonBlock\":11},\"baseFeePerGas\":\"0xa\"}"); - assertThat(londonGenesis.getForks()).hasSize(1); + assertThat(londonGenesis.getForkBlockNumbers()).hasSize(1); assertThat(londonGenesis.getConfigOptions().getMergeNetSplitBlockNumber()).isEmpty(); } @@ -355,7 +355,7 @@ public void testOverridePresent() { override.put("chainId", bigBlockString); override.put("contractSizeLimit", bigBlockString); - assertThat(config.getForks()).isNotEmpty(); + assertThat(config.getForkBlockNumbers()).isNotEmpty(); assertThat(config.getConfigOptions(override).getIstanbulBlockNumber()).hasValue(bigBlock); assertThat(config.getConfigOptions(override).getChainId()) .hasValue(BigInteger.valueOf(bigBlock)); @@ -370,7 +370,7 @@ public void testOverrideNull() { override.put("chainId", null); override.put("contractSizeLimit", null); - assertThat(config.getForks()).isNotEmpty(); + assertThat(config.getForkBlockNumbers()).isNotEmpty(); assertThat(config.getConfigOptions(override).getIstanbulBlockNumber()).isNotPresent(); assertThat(config.getConfigOptions(override).getChainId()).isNotPresent(); assertThat(config.getConfigOptions(override).getContractSizeLimit()).isNotPresent(); @@ -454,7 +454,7 @@ public void shouldLoadForksInSortedOrder() throws IOException { final GenesisConfigFile config = fromConfig(configNode); - assertThat(config.getForks()).containsExactly(1L, 2L, 3L, 1035301L, 2222222L); + assertThat(config.getForkBlockNumbers()).containsExactly(1L, 2L, 3L, 1035301L, 2222222L); assertThat(config.getConfigOptions().getChainId()).hasValue(BigInteger.valueOf(4)); } @@ -474,7 +474,7 @@ public void shouldLoadForksIgnoreClassicForkBlock() throws IOException { StandardCharsets.UTF_8))); final GenesisConfigFile config = fromConfig(configNode); - assertThat(config.getForks()).containsExactly(1L, 2L, 3L, 1035301L); + assertThat(config.getForkBlockNumbers()).containsExactly(1L, 2L, 3L, 1035301L); assertThat(config.getConfigOptions().getChainId()).hasValue(BigInteger.valueOf(61)); } @@ -522,10 +522,12 @@ public void shouldLoadForksIgnoreUnexpectedValues() throws IOException { final GenesisConfigFile configFileMultipleUnexpectedForks = fromConfig(configMultipleUnexpectedForks); - assertThat(configFileNoUnexpectedForks.getForks()).containsExactly(1L, 2L, 3L, 1035301L); - assertThat(configFileNoUnexpectedForks.getForks()).isEqualTo(configFileClassicFork.getForks()); - assertThat(configFileNoUnexpectedForks.getForks()) - .isEqualTo(configFileMultipleUnexpectedForks.getForks()); + assertThat(configFileNoUnexpectedForks.getForkBlockNumbers()) + .containsExactly(1L, 2L, 3L, 1035301L); + assertThat(configFileNoUnexpectedForks.getForkBlockNumbers()) + .isEqualTo(configFileClassicFork.getForkBlockNumbers()); + assertThat(configFileNoUnexpectedForks.getForkBlockNumbers()) + .isEqualTo(configFileMultipleUnexpectedForks.getForkBlockNumbers()); assertThat(configFileNoUnexpectedForks.getConfigOptions().getChainId()) .hasValue(BigInteger.valueOf(61)); } diff --git a/config/src/test/java/org/hyperledger/besu/config/GenesisConfigOptionsTest.java b/config/src/test/java/org/hyperledger/besu/config/GenesisConfigOptionsTest.java index d03ca747b7c..1dd54b138b6 100644 --- a/config/src/test/java/org/hyperledger/besu/config/GenesisConfigOptionsTest.java +++ b/config/src/test/java/org/hyperledger/besu/config/GenesisConfigOptionsTest.java @@ -197,6 +197,18 @@ public void shouldGetGrayGlacierBlockNumber() { assertThat(config.getGrayGlacierBlockNumber()).hasValue(4242); } + @Test + public void shouldGetShanghaiTime() { + final GenesisConfigOptions config = fromConfigOptions(singletonMap("shanghaiTime", 1670470141)); + assertThat(config.getShanghaiTime()).hasValue(1670470141); + } + + @Test + public void shouldGetCancunTime() { + final GenesisConfigOptions config = fromConfigOptions(singletonMap("cancunTime", 1670470142)); + assertThat(config.getCancunTime()).hasValue(1670470142); + } + @Test public void shouldGetShandongBlockNumber() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("shandongBlock", 1337)); diff --git a/config/src/test/resources/all_forks.json b/config/src/test/resources/all_forks.json index fee4fa4bda5..79d2ee4f35a 100644 --- a/config/src/test/resources/all_forks.json +++ b/config/src/test/resources/all_forks.json @@ -14,6 +14,8 @@ "arrowGlacierBlock": 12, "grayGlacierBlock": 13, "mergeNetSplitBlock": 14, + "shanghaiTime": 15, + "cancunTime": 16, "ecip1015Block": 102, "dieHardBlock": 103, "gothamBlock": 104, diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/CliqueBlockChoiceTests.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/CliqueBlockChoiceTests.java index 482152fc91d..cfa66ea500a 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/CliqueBlockChoiceTests.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/CliqueBlockChoiceTests.java @@ -37,6 +37,7 @@ import java.util.Comparator; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -54,7 +55,8 @@ public class CliqueBlockChoiceTests { private Block createEmptyBlock(final KeyPair blockSigner) { final BlockHeader header = TestHelpers.createCliqueSignedBlockHeader(headerBuilder, blockSigner, addresses); - return new Block(header, new BlockBody(Lists.newArrayList(), Lists.newArrayList())); + return new Block( + header, new BlockBody(Lists.newArrayList(), Lists.newArrayList(), Optional.empty())); } @Before diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/NodeCanProduceNextBlockTest.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/NodeCanProduceNextBlockTest.java index 4fbc5edaa26..52e7788461a 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/NodeCanProduceNextBlockTest.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/NodeCanProduceNextBlockTest.java @@ -37,6 +37,7 @@ import org.hyperledger.besu.ethereum.core.Util; import java.util.List; +import java.util.Optional; import com.google.common.collect.Lists; import org.junit.Before; @@ -59,7 +60,8 @@ public class NodeCanProduceNextBlockTest { private Block createEmptyBlock(final KeyPair blockSigner) { final BlockHeader header = TestHelpers.createCliqueSignedBlockHeader(headerBuilder, blockSigner, validatorList); - return new Block(header, new BlockBody(Lists.newArrayList(), Lists.newArrayList())); + return new Block( + header, new BlockBody(Lists.newArrayList(), Lists.newArrayList(), Optional.empty())); } @Before diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreatorTest.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreatorTest.java index 15e7bfd076f..2bfe550d57e 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreatorTest.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreatorTest.java @@ -115,7 +115,7 @@ public void setup() { new Block( TestHelpers.createCliqueSignedBlockHeader( headerTestFixture, otherKeyPair, validatorList), - new BlockBody(Lists.newArrayList(), Lists.newArrayList())); + new BlockBody(Lists.newArrayList(), Lists.newArrayList(), Optional.empty())); blockchain.appendBlock(emptyBlock, Lists.newArrayList()); } diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMiningCoordinatorTest.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMiningCoordinatorTest.java index 7cec20cf7c9..8c96a6528f2 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMiningCoordinatorTest.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMiningCoordinatorTest.java @@ -249,6 +249,7 @@ private Block createEmptyBlock( headerTestFixture.number(blockNumber).parentHash(parentHash); final BlockHeader header = TestHelpers.createCliqueSignedBlockHeader(headerTestFixture, signer, validators); - return new Block(header, new BlockBody(Collections.emptyList(), Collections.emptyList())); + return new Block( + header, new BlockBody(Collections.emptyList(), Collections.emptyList(), Optional.empty())); } } diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyCacheTestBase.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyCacheTestBase.java index 1a3dba12b1d..d0a209fdfe3 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyCacheTestBase.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyCacheTestBase.java @@ -30,6 +30,7 @@ import java.util.Collections; import java.util.List; +import java.util.Optional; import org.apache.tuweni.bytes.Bytes; import org.assertj.core.util.Lists; @@ -43,7 +44,7 @@ protected Block createEmptyBlock(final long blockNumber, final Hash parentHash) headerBuilder.number(blockNumber).parentHash(parentHash).coinbase(AddressHelpers.ofValue(0)); return new Block( headerBuilder.buildHeader(), - new BlockBody(Collections.emptyList(), Collections.emptyList())); + new BlockBody(Collections.emptyList(), Collections.emptyList(), Optional.empty())); } protected MutableBlockchain blockChain; diff --git a/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContextBuilder.java b/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContextBuilder.java index fbe42840b07..9566d093640 100644 --- a/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContextBuilder.java +++ b/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContextBuilder.java @@ -280,7 +280,8 @@ private static Block createGenesisBlock(final Set
validators) { final BlockHeader genesisHeader = headerTestFixture.buildHeader(); return new Block( - genesisHeader, new BlockBody(Collections.emptyList(), Collections.emptyList())); + genesisHeader, + new BlockBody(Collections.emptyList(), Collections.emptyList(), Optional.empty())); } private static ControllerAndState createControllerAndFinalState( diff --git a/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/tests/round/IbftRoundIntegrationTest.java b/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/tests/round/IbftRoundIntegrationTest.java index 3324e90c5b0..51376e1db02 100644 --- a/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/tests/round/IbftRoundIntegrationTest.java +++ b/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/tests/round/IbftRoundIntegrationTest.java @@ -110,7 +110,7 @@ public void setup() { headerTestFixture.extraData(bftExtraDataEncoder.encode(proposedExtraData)); headerTestFixture.number(1); final BlockHeader header = headerTestFixture.buildHeader(); - proposedBlock = new Block(header, new BlockBody(emptyList(), emptyList())); + proposedBlock = new Block(header, new BlockBody(emptyList(), emptyList(), Optional.empty())); when(blockImporter.importBlock(any(), any(), any())).thenReturn(new BlockImportResult(true)); diff --git a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftBlockHeightManagerTest.java b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftBlockHeightManagerTest.java index 1725867930d..18720ad0c40 100644 --- a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftBlockHeightManagerTest.java +++ b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftBlockHeightManagerTest.java @@ -123,7 +123,7 @@ private void buildCreatedBlock() { headerTestFixture.extraData(new IbftExtraDataCodec().encode(extraData)); final BlockHeader header = headerTestFixture.buildHeader(); - createdBlock = new Block(header, new BlockBody(emptyList(), emptyList())); + createdBlock = new Block(header, new BlockBody(emptyList(), emptyList(), Optional.empty())); } @Before diff --git a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRoundTest.java b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRoundTest.java index 3826098ca4e..0ee65bb89ff 100644 --- a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRoundTest.java +++ b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRoundTest.java @@ -119,7 +119,7 @@ public void setup() { headerTestFixture.number(1); final BlockHeader header = headerTestFixture.buildHeader(); - proposedBlock = new Block(header, new BlockBody(emptyList(), emptyList())); + proposedBlock = new Block(header, new BlockBody(emptyList(), emptyList(), Optional.empty())); when(blockCreator.createBlock(anyLong())) .thenReturn(new BlockCreationResult(proposedBlock, new TransactionSelectionResults())); diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/MergeProtocolSchedule.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/MergeProtocolSchedule.java index 01133d841db..ec2ee95f4df 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/MergeProtocolSchedule.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/MergeProtocolSchedule.java @@ -22,6 +22,8 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecBuilder; +import org.hyperledger.besu.ethereum.mainnet.TimestampSchedule; +import org.hyperledger.besu.ethereum.mainnet.TimestampScheduleBuilder; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.evm.MainnetEVMs; import org.hyperledger.besu.evm.internal.EvmConfiguration; @@ -58,6 +60,35 @@ public static ProtocolSchedule create( .createProtocolSchedule(); } + public static TimestampSchedule createTimestamp( + final GenesisConfigOptions config, + final PrivacyParameters privacyParameters, + final boolean isRevertReasonEnabled) { + return new TimestampScheduleBuilder( + config, + DEFAULT_CHAIN_ID, + ProtocolSpecAdapters.create( + config.getShanghaiTime().orElse(0), + MergeProtocolSchedule::applyMergeSpecificModificationsForShanghai), + privacyParameters, + isRevertReasonEnabled, + config.isQuorum(), + EvmConfiguration.DEFAULT) + .createTimestampSchedule(); + } + + // TODO Withdrawals remove this as part of https://github.com/hyperledger/besu/issues/4788 + private static ProtocolSpecBuilder applyMergeSpecificModificationsForShanghai( + final ProtocolSpecBuilder specBuilder) { + + return specBuilder + .blockProcessorBuilder(MergeBlockProcessor::new) + .blockHeaderValidatorBuilder(MergeProtocolSchedule::getBlockHeaderValidator) + .blockReward(Wei.ZERO) + .difficultyCalculator((a, b, c) -> BigInteger.ZERO) + .skipZeroBlockRewards(true); + } + private static ProtocolSpecBuilder applyMergeSpecificModifications( final ProtocolSpecBuilder specBuilder, final Optional chainId) { diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionBackwardSyncContext.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionBackwardSyncContext.java index fe6bb8860f6..8264af35af5 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionBackwardSyncContext.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionBackwardSyncContext.java @@ -53,8 +53,6 @@ public TransitionBackwardSyncContext( */ @Override public BlockValidator getBlockValidatorForBlock(final Block block) { - return transitionProtocolSchedule - .getByBlockHeader(protocolContext, block.getHeader()) - .getBlockValidator(); + return transitionProtocolSchedule.getByBlockHeader(block.getHeader()).getBlockValidator(); } } diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionProtocolSchedule.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionProtocolSchedule.java index 75e03ae4dfb..181271ee965 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionProtocolSchedule.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionProtocolSchedule.java @@ -17,12 +17,14 @@ import static org.hyperledger.besu.util.Slf4jLambdaHelper.debugLambda; import org.hyperledger.besu.ethereum.ProtocolContext; -import org.hyperledger.besu.ethereum.chain.MutableBlockchain; -import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.core.TransactionFilter; +import org.hyperledger.besu.ethereum.mainnet.HeaderBasedProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.mainnet.TimestampSchedule; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import java.math.BigInteger; @@ -32,33 +34,41 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class TransitionProtocolSchedule extends TransitionUtils - implements ProtocolSchedule { +public class TransitionProtocolSchedule implements ProtocolSchedule { + private final TransitionUtils transitionUtils; private static final Logger LOG = LoggerFactory.getLogger(TransitionProtocolSchedule.class); - - public TransitionProtocolSchedule( - final ProtocolSchedule preMergeProtocolSchedule, - final ProtocolSchedule postMergeProtocolSchedule) { - super(preMergeProtocolSchedule, postMergeProtocolSchedule); - } + private final TimestampSchedule timestampSchedule; + private final MergeContext mergeContext; + private ProtocolContext protocolContext; public TransitionProtocolSchedule( final ProtocolSchedule preMergeProtocolSchedule, final ProtocolSchedule postMergeProtocolSchedule, - final MergeContext mergeContext) { - super(preMergeProtocolSchedule, postMergeProtocolSchedule, mergeContext); + final MergeContext mergeContext, + final TimestampSchedule timestampSchedule) { + this.timestampSchedule = timestampSchedule; + this.mergeContext = mergeContext; + transitionUtils = + new TransitionUtils<>(preMergeProtocolSchedule, postMergeProtocolSchedule, mergeContext); } public ProtocolSchedule getPreMergeSchedule() { - return getPreMergeObject(); + return transitionUtils.getPreMergeObject(); } public ProtocolSchedule getPostMergeSchedule() { - return getPostMergeObject(); + return transitionUtils.getPostMergeObject(); } - public ProtocolSpec getByBlockHeader( - final ProtocolContext protocolContext, final BlockHeader blockHeader) { + @Override + public ProtocolSpec getByBlockHeader(final ProcessableBlockHeader blockHeader) { + return this.timestampSchedule + .getByTimestamp(blockHeader.getTimestamp()) + .orElseGet(() -> getByBlockHeaderFromTransitionUtils(blockHeader)); + } + + private ProtocolSpec getByBlockHeaderFromTransitionUtils( + final ProcessableBlockHeader blockHeader) { // if we do not have a finalized block we might return pre or post merge protocol schedule: if (mergeContext.getFinalized().isEmpty()) { @@ -72,9 +82,11 @@ public ProtocolSpec getByBlockHeader( } // otherwise check to see if this block represents a re-org TTD block: - MutableBlockchain blockchain = protocolContext.getBlockchain(); Difficulty parentDifficulty = - blockchain.getTotalDifficultyByHash(blockHeader.getParentHash()).orElseThrow(); + protocolContext + .getBlockchain() + .getTotalDifficultyByHash(blockHeader.getParentHash()) + .orElseThrow(); Difficulty thisDifficulty = parentDifficulty.add(blockHeader.getDifficulty()); Difficulty terminalDifficulty = mergeContext.getTerminalTotalDifficulty(); debugLambda( @@ -102,32 +114,61 @@ public ProtocolSpec getByBlockHeader( @Override public ProtocolSpec getByBlockNumber(final long number) { - return dispatchFunctionAccordingToMergeState( - protocolSchedule -> protocolSchedule.getByBlockNumber(number)); + + return Optional.ofNullable(protocolContext) + .map(ProtocolContext::getBlockchain) + .flatMap(blockchain -> blockchain.getBlockByNumber(number)) + .map(Block::getHeader) + .map(timestampSchedule::getByBlockHeader) + .orElseGet( + () -> + transitionUtils.dispatchFunctionAccordingToMergeState( + protocolSchedule -> protocolSchedule.getByBlockNumber(number))); } @Override public Stream streamMilestoneBlocks() { - return dispatchFunctionAccordingToMergeState(ProtocolSchedule::streamMilestoneBlocks); + return transitionUtils.dispatchFunctionAccordingToMergeState( + ProtocolSchedule::streamMilestoneBlocks); } @Override public Optional getChainId() { - return dispatchFunctionAccordingToMergeState(ProtocolSchedule::getChainId); + return transitionUtils.dispatchFunctionAccordingToMergeState(ProtocolSchedule::getChainId); + } + + @Override + public void putMilestone(final long blockOrTimestamp, final ProtocolSpec protocolSpec) { + throw new UnsupportedOperationException( + "Should not use TransitionProtocolSchedule wrapper class to create milestones"); + } + + @Override + public String listMilestones() { + String blockNumberMilestones = + transitionUtils.dispatchFunctionAccordingToMergeState( + HeaderBasedProtocolSchedule::listMilestones); + return blockNumberMilestones + ";" + timestampSchedule.listMilestones(); } @Override public void setTransactionFilter(final TransactionFilter transactionFilter) { - dispatchConsumerAccordingToMergeState( + timestampSchedule.setTransactionFilter(transactionFilter); + transitionUtils.dispatchConsumerAccordingToMergeState( protocolSchedule -> protocolSchedule.setTransactionFilter(transactionFilter)); } @Override public void setPublicWorldStateArchiveForPrivacyBlockProcessor( final WorldStateArchive publicWorldStateArchive) { - dispatchConsumerAccordingToMergeState( + timestampSchedule.setPublicWorldStateArchiveForPrivacyBlockProcessor(publicWorldStateArchive); + transitionUtils.dispatchConsumerAccordingToMergeState( protocolSchedule -> protocolSchedule.setPublicWorldStateArchiveForPrivacyBlockProcessor( publicWorldStateArchive)); } + + public void setProtocolContext(final ProtocolContext protocolContext) { + this.protocolContext = protocolContext; + } } diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionUtils.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionUtils.java index 0560711ae13..9e19cd28b75 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionUtils.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionUtils.java @@ -17,8 +17,8 @@ import static org.hyperledger.besu.util.Slf4jLambdaHelper.warnLambda; import org.hyperledger.besu.ethereum.ProtocolContext; -import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import java.util.Optional; import java.util.function.Consumer; @@ -27,18 +27,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public abstract class TransitionUtils { +public class TransitionUtils { private static final Logger LOG = LoggerFactory.getLogger(TransitionUtils.class); protected final MergeContext mergeContext; private final SwitchingObject preMergeObject; private final SwitchingObject postMergeObject; - public TransitionUtils( - final SwitchingObject preMergeObject, final SwitchingObject postMergeObject) { - this(preMergeObject, postMergeObject, PostMergeContext.get()); - } - public TransitionUtils( final SwitchingObject preMergeObject, final SwitchingObject postMergeObject, @@ -66,7 +61,7 @@ SwitchingObject getPostMergeObject() { } public static boolean isTerminalProofOfWorkBlock( - final BlockHeader header, final ProtocolContext context) { + final ProcessableBlockHeader header, final ProtocolContext context) { Difficulty headerDifficulty = Optional.ofNullable(header.getDifficulty()).orElse(Difficulty.ZERO); @@ -102,4 +97,8 @@ public static boolean isTerminalProofOfWorkBlock( return header.getNumber() == 0L && header.getDifficulty().greaterOrEqualThan(configuredTotalTerminalDifficulty); } + + public MergeContext getMergeContext() { + return mergeContext; + } } diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeBlockCreator.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeBlockCreator.java index e2ea65fe98d..24e4d8d4db9 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeBlockCreator.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeBlockCreator.java @@ -23,6 +23,7 @@ import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.SealableBlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; @@ -62,10 +63,12 @@ public class MergeBlockCreator extends AbstractBlockCreator { public BlockCreationResult createBlock( final Optional> maybeTransactions, final Bytes32 random, - final long timestamp) { + final long timestamp, + final Optional> withdrawals) { return createBlock( maybeTransactions, Optional.of(Collections.emptyList()), + withdrawals, Optional.of(random), timestamp, false); 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 f4350b02a3c..ad914876fd9 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 @@ -16,7 +16,6 @@ import static org.hyperledger.besu.consensus.merge.TransitionUtils.isTerminalProofOfWorkBlock; import static org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator.ForkchoiceResult.Status.INVALID; -import static org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator.ForkchoiceResult.Status.INVALID_PAYLOAD_ATTRIBUTES; import static org.hyperledger.besu.util.Slf4jLambdaHelper.debugLambda; import org.hyperledger.besu.consensus.merge.MergeContext; @@ -34,6 +33,7 @@ import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.eth.sync.backwardsync.BackwardSyncContext; import org.hyperledger.besu.ethereum.eth.sync.backwardsync.BadChainListener; import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter; @@ -208,7 +208,8 @@ public PayloadIdentifier preparePayload( final BlockHeader parentHeader, final Long timestamp, final Bytes32 prevRandao, - final Address feeRecipient) { + final Address feeRecipient, + final Optional> withdrawals) { // we assume that preparePayload is always called sequentially, since the RPC Engine calls // are sequential, if this assumption changes then more synchronization should be added to @@ -233,7 +234,7 @@ public PayloadIdentifier preparePayload( // put the empty block in first final Block emptyBlock = mergeBlockCreator - .createBlock(Optional.of(Collections.emptyList()), prevRandao, timestamp) + .createBlock(Optional.of(Collections.emptyList()), prevRandao, timestamp, withdrawals) .getBlock(); BlockProcessingResult result = validateBlock(emptyBlock); @@ -254,7 +255,7 @@ public PayloadIdentifier preparePayload( } } - tryToBuildBetterBlock(timestamp, prevRandao, payloadIdentifier, mergeBlockCreator); + tryToBuildBetterBlock(timestamp, prevRandao, payloadIdentifier, mergeBlockCreator, withdrawals); return payloadIdentifier; } @@ -274,10 +275,11 @@ private void tryToBuildBetterBlock( final Long timestamp, final Bytes32 random, final PayloadIdentifier payloadIdentifier, - final MergeBlockCreator mergeBlockCreator) { + final MergeBlockCreator mergeBlockCreator, + final Optional> withdrawals) { final Supplier blockCreator = - () -> mergeBlockCreator.createBlock(Optional.empty(), random, timestamp); + () -> mergeBlockCreator.createBlock(Optional.empty(), random, timestamp, withdrawals); LOG.debug( "Block creation started for payload id {}, remaining time is {}ms", @@ -355,7 +357,7 @@ private void evaluateNewBlock( if (isBlockCreationCancelled(payloadIdentifier)) return; - final var resultBest = validateBlock(bestBlock); + final BlockProcessingResult resultBest = validateBlock(bestBlock); if (resultBest.isSuccessful()) { if (isBlockCreationCancelled(payloadIdentifier)) return; @@ -377,6 +379,8 @@ private void evaluateNewBlock( if (resultBest.causedBy().isPresent()) { LOG.warn("caused by", resultBest.cause.get()); } + LOG.warn("Block {} yield: {}", bestBlock.getHash(), resultBest.getYield()); + LOG.warn("Block {} receipts: {}", bestBlock.getHash(), resultBest.getReceipts()); } } @@ -435,7 +439,7 @@ private void updateFinalized(final Hash finalizedHash) { public BlockProcessingResult validateBlock(final Block block) { final var validationResult = protocolSchedule - .getByBlockNumber(block.getHeader().getNumber()) + .getByBlockHeader(block.getHeader()) .getBlockValidator() .validateAndProcessBlock( protocolContext, @@ -462,10 +466,7 @@ public BlockProcessingResult rememberBlock(final Block block) { @Override public ForkchoiceResult updateForkChoice( - final BlockHeader newHead, - final Hash finalizedBlockHash, - final Hash safeBlockHash, - final Optional maybePayloadAttributes) { + final BlockHeader newHead, final Hash finalizedBlockHash, final Hash safeBlockHash) { MutableBlockchain blockchain = protocolContext.getBlockchain(); final Optional newFinalized = blockchain.getBlockHeader(finalizedBlockHash); @@ -501,11 +502,6 @@ && isDescendantOf(newHead, blockchain.getChainHeadHeader())) { mergeContext.setSafeBlock(newSafeBlock); }); - if (maybePayloadAttributes.isPresent() - && !isPayloadAttributesValid(maybePayloadAttributes.get(), newHead)) { - return ForkchoiceResult.withFailure(INVALID_PAYLOAD_ATTRIBUTES, null, Optional.empty()); - } - return ForkchoiceResult.withResult(newFinalized, Optional.of(newHead)); } @@ -725,11 +721,6 @@ public boolean isDescendantOf(final BlockHeader ancestorBlock, final BlockHeader } } - private boolean isPayloadAttributesValid( - final PayloadAttributes payloadAttributes, final BlockHeader headBlockHeader) { - return payloadAttributes.getTimestamp() > headBlockHeader.getTimestamp(); - } - @Override public void onBadChain( final Block badBlock, diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeMiningCoordinator.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeMiningCoordinator.java index 79fedf90734..92ecde01c8d 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeMiningCoordinator.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeMiningCoordinator.java @@ -23,7 +23,9 @@ import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.Withdrawal; +import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -34,7 +36,8 @@ PayloadIdentifier preparePayload( final BlockHeader parentHeader, final Long timestamp, final Bytes32 prevRandao, - final Address feeRecipient); + final Address feeRecipient, + final Optional> withdrawals); @Override default boolean isCompatibleWithEngineApi() { @@ -46,10 +49,7 @@ default boolean isCompatibleWithEngineApi() { BlockProcessingResult validateBlock(final Block block); ForkchoiceResult updateForkChoice( - final BlockHeader newHead, - final Hash finalizedBlockHash, - final Hash safeBlockHash, - final Optional maybePayloadAttributes); + final BlockHeader newHead, final Hash finalizedBlockHash, final Hash safeBlockHash); Optional getLatestValidAncestor(Hash blockHash); diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/PayloadAttributes.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/PayloadAttributes.java index b9b77beddd9..28f33f32dfc 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/PayloadAttributes.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/PayloadAttributes.java @@ -15,6 +15,9 @@ package org.hyperledger.besu.consensus.merge.blockcreation; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.core.Withdrawal; + +import java.util.List; import org.apache.tuweni.bytes.Bytes32; @@ -22,12 +25,17 @@ public class PayloadAttributes { private final Long timestamp; private final Bytes32 prevRandao; private final Address suggestedFeeRecipient; + private final List withdrawals; public PayloadAttributes( - final Long timestamp, final Bytes32 prevRandao, final Address suggestedFeeRecipient) { + final Long timestamp, + final Bytes32 prevRandao, + final Address suggestedFeeRecipient, + final List withdrawals) { this.timestamp = timestamp; this.prevRandao = prevRandao; this.suggestedFeeRecipient = suggestedFeeRecipient; + this.withdrawals = withdrawals; } public Long getTimestamp() { @@ -41,4 +49,8 @@ public Bytes32 getPrevRandao() { public Address getSuggestedFeeRecipient() { return suggestedFeeRecipient; } + + public List getWithdrawals() { + return withdrawals; + } } diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/TransitionCoordinator.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/TransitionCoordinator.java index ce9e84e7dd2..0140e22bbd4 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/TransitionCoordinator.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/TransitionCoordinator.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.consensus.merge.blockcreation; +import org.hyperledger.besu.consensus.merge.PostMergeContext; import org.hyperledger.besu.consensus.merge.TransitionUtils; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; @@ -25,6 +26,7 @@ import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.Withdrawal; import java.util.List; import java.util.Optional; @@ -41,7 +43,7 @@ public class TransitionCoordinator extends TransitionUtils public TransitionCoordinator( final MiningCoordinator miningCoordinator, final MiningCoordinator mergeCoordinator) { - super(miningCoordinator, mergeCoordinator); + super(miningCoordinator, mergeCoordinator, PostMergeContext.get()); this.miningCoordinator = miningCoordinator; this.mergeCoordinator = (MergeMiningCoordinator) mergeCoordinator; } @@ -132,8 +134,10 @@ public PayloadIdentifier preparePayload( final BlockHeader parentHeader, final Long timestamp, final Bytes32 prevRandao, - final Address feeRecipient) { - return mergeCoordinator.preparePayload(parentHeader, timestamp, prevRandao, feeRecipient); + final Address feeRecipient, + final Optional> withdrawals) { + return mergeCoordinator.preparePayload( + parentHeader, timestamp, prevRandao, feeRecipient, withdrawals); } @Override @@ -148,12 +152,8 @@ public BlockProcessingResult validateBlock(final Block block) { @Override public ForkchoiceResult updateForkChoice( - final BlockHeader newHead, - final Hash finalizedBlockHash, - final Hash safeBlockHash, - final Optional maybePayloadAttributes) { - return mergeCoordinator.updateForkChoice( - newHead, finalizedBlockHash, safeBlockHash, maybePayloadAttributes); + final BlockHeader newHead, final Hash finalizedBlockHash, final Hash safeBlockHash) { + return mergeCoordinator.updateForkChoice(newHead, finalizedBlockHash, safeBlockHash); } @Override diff --git a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/TransitionProtocolScheduleTest.java b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/TransitionProtocolScheduleTest.java index 66aa2a31ce0..9c43a9d202e 100644 --- a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/TransitionProtocolScheduleTest.java +++ b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/TransitionProtocolScheduleTest.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.consensus.merge; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; @@ -22,9 +23,13 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.mainnet.TimestampSchedule; import java.util.Optional; @@ -42,10 +47,13 @@ public class TransitionProtocolScheduleTest { @Mock MergeContext mergeContext; @Mock ProtocolSchedule preMergeProtocolSchedule; @Mock ProtocolSchedule postMergeProtocolSchedule; + + @Mock TimestampSchedule timestampSchedule; @Mock BlockHeader blockHeader; private static final Difficulty TTD = Difficulty.of(100L); private static final long BLOCK_NUMBER = 29L; + private static final long TIMESTAMP = 1L; private TransitionProtocolSchedule transitionProtocolSchedule; @Before @@ -53,10 +61,13 @@ public void setUp() { when(protocolContext.getBlockchain()).thenReturn(blockchain); when(protocolContext.getConsensusContext(MergeContext.class)).thenReturn(mergeContext); when(mergeContext.getTerminalTotalDifficulty()).thenReturn(TTD); + when(blockHeader.getTimestamp()).thenReturn(TIMESTAMP); + when(timestampSchedule.getByTimestamp(TIMESTAMP)).thenReturn(Optional.empty()); transitionProtocolSchedule = new TransitionProtocolSchedule( - preMergeProtocolSchedule, postMergeProtocolSchedule, mergeContext); + preMergeProtocolSchedule, postMergeProtocolSchedule, mergeContext, timestampSchedule); + transitionProtocolSchedule.setProtocolContext(protocolContext); } @Test @@ -64,7 +75,7 @@ public void returnPostMergeIfFinalizedExists() { when(mergeContext.getFinalized()).thenReturn(Optional.of(mock(BlockHeader.class))); when(blockHeader.getNumber()).thenReturn(BLOCK_NUMBER); - transitionProtocolSchedule.getByBlockHeader(protocolContext, blockHeader); + transitionProtocolSchedule.getByBlockHeader(blockHeader); verifyPostMergeProtocolScheduleReturned(); } @@ -76,7 +87,7 @@ public void returnPreMergeIfBeforeMerge() { when(blockHeader.getNumber()).thenReturn(BLOCK_NUMBER); - transitionProtocolSchedule.getByBlockHeader(protocolContext, blockHeader); + transitionProtocolSchedule.getByBlockHeader(blockHeader); verifyPreMergeProtocolScheduleReturned(); } @@ -95,7 +106,7 @@ public void returnPreMergeIfTerminalPoWBlock() { when(blockchain.getTotalDifficultyByHash(parentHash)) .thenReturn(Optional.of(Difficulty.of(95L))); - transitionProtocolSchedule.getByBlockHeader(protocolContext, blockHeader); + transitionProtocolSchedule.getByBlockHeader(blockHeader); verifyPreMergeProtocolScheduleReturned(); } @@ -114,7 +125,7 @@ public void returnPreMergeIfAfterMergeButReorgPreTTD() { when(blockchain.getTotalDifficultyByHash(parentHash)) .thenReturn(Optional.of(Difficulty.of(95L))); - transitionProtocolSchedule.getByBlockHeader(protocolContext, blockHeader); + transitionProtocolSchedule.getByBlockHeader(blockHeader); verifyPreMergeProtocolScheduleReturned(); } @@ -133,7 +144,61 @@ public void returnPostMergeIfAfterMergeButReorgPostTTD() { when(blockchain.getTotalDifficultyByHash(parentHash)) .thenReturn(Optional.of(Difficulty.of(105L))); - transitionProtocolSchedule.getByBlockHeader(protocolContext, blockHeader); + transitionProtocolSchedule.getByBlockHeader(blockHeader); + + verifyPostMergeProtocolScheduleReturned(); + } + + @Test + public void getByBlockHeader_returnsTimestampScheduleIfPresent() { + when(timestampSchedule.getByTimestamp(TIMESTAMP)) + .thenReturn(Optional.of(mock(ProtocolSpec.class))); + + assertThat(transitionProtocolSchedule.getByBlockHeader(blockHeader)).isNotNull(); + + verify(timestampSchedule).getByTimestamp(TIMESTAMP); + verifyNoMergeScheduleInteractions(); + } + + @Test + public void getByBlockNumber_returnsTimestampScheduleIfPresent() { + final Block block = new Block(blockHeader, BlockBody.empty()); + when(blockchain.getBlockByNumber(BLOCK_NUMBER)).thenReturn(Optional.of(block)); + when(timestampSchedule.getByBlockHeader(blockHeader)).thenReturn(mock(ProtocolSpec.class)); + + assertThat(transitionProtocolSchedule.getByBlockNumber(BLOCK_NUMBER)).isNotNull(); + + verify(timestampSchedule).getByBlockHeader(blockHeader); + verifyNoMergeScheduleInteractions(); + } + + @Test + public void getByBlockNumber_delegatesToPreMergeScheduleWhenBlockNotFound() { + when(blockchain.getBlockByNumber(BLOCK_NUMBER)).thenReturn(Optional.empty()); + when(mergeContext.isPostMerge()).thenReturn(false); + + transitionProtocolSchedule.getByBlockNumber(BLOCK_NUMBER); + + verifyPreMergeProtocolScheduleReturned(); + } + + @Test + public void getByBlockNumber_delegatesToPostMergeScheduleWhenBlockNotFound() { + when(blockchain.getBlockByNumber(BLOCK_NUMBER)).thenReturn(Optional.empty()); + when(mergeContext.isPostMerge()).thenReturn(true); + + transitionProtocolSchedule.getByBlockNumber(BLOCK_NUMBER); + + verifyPostMergeProtocolScheduleReturned(); + } + + @Test + public void getByBlockNumber_delegatesToPostMergeScheduleWhenTimestampScheduleDoesNotExist() { + final Block block = new Block(blockHeader, BlockBody.empty()); + when(blockchain.getBlockByNumber(BLOCK_NUMBER)).thenReturn(Optional.of(block)); + when(mergeContext.isPostMerge()).thenReturn(true); + + transitionProtocolSchedule.getByBlockNumber(BLOCK_NUMBER); verifyPostMergeProtocolScheduleReturned(); } @@ -147,4 +212,9 @@ private void verifyPostMergeProtocolScheduleReturned() { verify(postMergeProtocolSchedule).getByBlockNumber(BLOCK_NUMBER); verifyNoInteractions(preMergeProtocolSchedule); } + + private void verifyNoMergeScheduleInteractions() { + verifyNoInteractions(preMergeProtocolSchedule); + verifyNoInteractions(postMergeProtocolSchedule); + } } 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 074002ea2ba..d37223a68f9 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 @@ -60,6 +60,7 @@ import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionTestFixture; +import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.eth.sync.backwardsync.BackwardSyncContext; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.sorter.BaseFeePendingTransactionsSorter; @@ -73,6 +74,7 @@ import org.hyperledger.besu.testutil.TestClock; import java.time.ZoneId; +import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; @@ -110,6 +112,8 @@ public class MergeCoordinatorTest implements MergeGenesisConfigHelper { "ae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f")); private static final KeyPair KEYS1 = new KeyPair(PRIVATE_KEY1, SIGNATURE_ALGORITHM.get().createPublicKey(PRIVATE_KEY1)); + + private static final Optional> EMPTY_WITHDRAWALS = Optional.empty(); @Mock MergeContext mergeContext; @Mock BackwardSyncContext backwardSyncContext; @@ -213,7 +217,8 @@ public void coinbaseShouldMatchSuggestedFeeRecipient() { genesisState.getBlock().getHeader(), System.currentTimeMillis() / 1000, Bytes32.ZERO, - suggestedFeeRecipient); + suggestedFeeRecipient, + EMPTY_WITHDRAWALS); ArgumentCaptor block = ArgumentCaptor.forClass(Block.class); @@ -249,7 +254,7 @@ public void exceptionDuringBuildingBlockShouldNotBeInvalid() .doThrow(new MerkleTrieException("missing leaf")) .doCallRealMethod() .when(beingSpiedOn) - .createBlock(any(), any(Bytes32.class), anyLong()); + .createBlock(any(), any(Bytes32.class), anyLong(), any()); return beingSpiedOn; }; @@ -284,7 +289,8 @@ public void exceptionDuringBuildingBlockShouldNotBeInvalid() genesisState.getBlock().getHeader(), System.currentTimeMillis() / 1000, Bytes32.random(), - suggestedFeeRecipient); + suggestedFeeRecipient, + Optional.empty()); verify(willThrow, never()).addBadBlock(any(), any()); blockCreationTask.get(); @@ -325,7 +331,8 @@ public void shouldContinueBuildingBlocksUntilFinalizeIsCalled() genesisState.getBlock().getHeader(), System.currentTimeMillis() / 1000, Bytes32.ZERO, - suggestedFeeRecipient); + suggestedFeeRecipient, + EMPTY_WITHDRAWALS); blockCreationTask.get(); @@ -367,7 +374,8 @@ public void shouldRetryBlockCreationOnRecoverableError() genesisState.getBlock().getHeader(), System.currentTimeMillis() / 1000, Bytes32.ZERO, - suggestedFeeRecipient); + suggestedFeeRecipient, + EMPTY_WITHDRAWALS); blockCreationTask.get(); @@ -397,7 +405,8 @@ public void shouldStopRetryBlockCreationIfTimeExpired() throws InterruptedExcept genesisState.getBlock().getHeader(), System.currentTimeMillis() / 1000, Bytes32.ZERO, - suggestedFeeRecipient); + suggestedFeeRecipient, + EMPTY_WITHDRAWALS); try { blockCreationTask.get(); @@ -438,7 +447,8 @@ public void shouldStopInProgressBlockCreationIfFinalizedIsCalled() genesisState.getBlock().getHeader(), System.currentTimeMillis() / 1000, Bytes32.ZERO, - suggestedFeeRecipient); + suggestedFeeRecipient, + EMPTY_WITHDRAWALS); waitForBlockCreationInProgress.await(); coordinator.finalizeProposalById(payloadId); @@ -478,13 +488,21 @@ public void shouldNotStartAnotherBlockCreationJobIfCalledAgainWithTheSamePayload var payloadId1 = coordinator.preparePayload( - genesisState.getBlock().getHeader(), timestamp, Bytes32.ZERO, suggestedFeeRecipient); + genesisState.getBlock().getHeader(), + timestamp, + Bytes32.ZERO, + suggestedFeeRecipient, + EMPTY_WITHDRAWALS); final CompletableFuture task1 = blockCreationTask; var payloadId2 = coordinator.preparePayload( - genesisState.getBlock().getHeader(), timestamp, Bytes32.ZERO, suggestedFeeRecipient); + genesisState.getBlock().getHeader(), + timestamp, + Bytes32.ZERO, + suggestedFeeRecipient, + EMPTY_WITHDRAWALS); assertThat(payloadId1).isEqualTo(payloadId2); @@ -520,7 +538,7 @@ public void childTimestampExceedsParentsFails() { ForkchoiceResult result = coordinator.updateForkChoice( - childHeader, terminalHeader.getHash(), terminalHeader.getHash(), Optional.empty()); + childHeader, terminalHeader.getHash(), terminalHeader.getHash()); assertThat(result.isValid()).isFalse(); assertThat(result.getErrorMessage()).isPresent(); @@ -640,10 +658,7 @@ public void onBlockAdded(final BlockAddedEvent event) { } coordinator.updateForkChoice( - prevParent, - genesisState.getBlock().getHash(), - genesisState.getBlock().getHash(), - Optional.empty()); + prevParent, genesisState.getBlock().getHash(), genesisState.getBlock().getHash()); Hash expectedCommonAncestor = blockchain.getBlockHeader(2).get().getBlockHash(); // generate from 3' down to some other head. Remeber those. @@ -657,10 +672,7 @@ public void onBlockAdded(final BlockAddedEvent event) { prevParent = nextPrime; } coordinator.updateForkChoice( - prevParent, - genesisState.getBlock().getHash(), - genesisState.getBlock().getHash(), - Optional.empty()); + prevParent, genesisState.getBlock().getHash(), genesisState.getBlock().getHash()); assertThat(lastBlockAddedEvent.get().getCommonAncestorHash()).isEqualTo(expectedCommonAncestor); assertThat(lastBlockAddedEvent.get().getEventType()).isEqualTo(EventType.CHAIN_REORG); assertThat(lastBlockAddedEvent.get().getBlock().getHash()).isEqualTo(prevParent.getBlockHash()); @@ -859,45 +871,6 @@ public void assertMergeAtGenesisSatisifiesTerminalPoW() { assertThat(mockCoordinator.latestValidAncestorDescendsFromTerminal(blockZero)).isFalse(); } - @Test - public void invalidPayloadShouldReturnErrorAndUpdateForkchoiceState() { - BlockHeader terminalHeader = terminalPowBlock(); - sendNewPayloadAndForkchoiceUpdate( - new Block(terminalHeader, BlockBody.empty()), Optional.empty(), Hash.ZERO); - - BlockHeader prevFinalizedHeader = nextBlockHeader(terminalHeader); - Block prevFinalizedBlock = new Block(prevFinalizedHeader, BlockBody.empty()); - sendNewPayloadAndForkchoiceUpdate( - prevFinalizedBlock, Optional.empty(), terminalHeader.getHash()); - - BlockHeader lastFinalizedHeader = nextBlockHeader(prevFinalizedHeader); - Block lastFinalizedBlock = new Block(lastFinalizedHeader, BlockBody.empty()); - - sendNewPayloadAndForkchoiceUpdate( - lastFinalizedBlock, Optional.of(prevFinalizedHeader), prevFinalizedHeader.getHash()); - - BlockHeader headBlockHeader = nextBlockHeader(lastFinalizedHeader); - Block headBlock = new Block(headBlockHeader, BlockBody.empty()); - assertThat(coordinator.rememberBlock(headBlock).getYield()).isPresent(); - - var res = - coordinator.updateForkChoice( - headBlockHeader, - lastFinalizedBlock.getHash(), - lastFinalizedBlock.getHash(), - Optional.of( - new PayloadAttributes( - headBlockHeader.getTimestamp() - 1, Hash.ZERO, Address.ZERO))); - - assertThat(res.isValid()).isFalse(); - assertThat(res.getStatus()).isEqualTo(ForkchoiceResult.Status.INVALID_PAYLOAD_ATTRIBUTES); - - verify(blockchain).setFinalized(lastFinalizedBlock.getHash()); - verify(mergeContext).setFinalized(lastFinalizedHeader); - verify(blockchain).setSafeBlock(lastFinalizedBlock.getHash()); - verify(mergeContext).setSafeBlock(lastFinalizedHeader); - } - @Test public void forkchoiceUpdateShouldIgnoreAncestorOfChainHead() { BlockHeader terminalHeader = terminalPowBlock(); @@ -913,12 +886,7 @@ public void forkchoiceUpdateShouldIgnoreAncestorOfChainHead() { sendNewPayloadAndForkchoiceUpdate(child, Optional.empty(), parent.getHash()); ForkchoiceResult res = - coordinator.updateForkChoice( - parentHeader, - Hash.ZERO, - terminalHeader.getHash(), - Optional.of( - new PayloadAttributes(parentHeader.getTimestamp() + 1, Hash.ZERO, Address.ZERO))); + coordinator.updateForkChoice(parentHeader, Hash.ZERO, terminalHeader.getHash()); assertThat(res.getStatus()).isEqualTo(ForkchoiceResult.Status.IGNORE_UPDATE_TO_OLD_HEAD); assertThat(res.getNewHead().isEmpty()).isTrue(); @@ -938,8 +906,7 @@ private void sendNewPayloadAndForkchoiceUpdate( .updateForkChoice( block.getHeader(), finalizedHeader.map(BlockHeader::getHash).orElse(Hash.ZERO), - safeHash, - Optional.empty()) + safeHash) .isValid()) .isTrue(); diff --git a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java index d12be771a75..b90e25ec677 100644 --- a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java +++ b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java @@ -361,7 +361,8 @@ private static Block createGenesisBlock(final Set
validators) { final BlockHeader genesisHeader = headerTestFixture.buildHeader(); return new Block( - genesisHeader, new BlockBody(Collections.emptyList(), Collections.emptyList())); + genesisHeader, + new BlockBody(Collections.emptyList(), Collections.emptyList(), Optional.empty())); } private GenesisState createGenesisBlock(final String genesisFile) throws IOException { diff --git a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/round/QbftRoundIntegrationTest.java b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/round/QbftRoundIntegrationTest.java index cd75bb0418f..567f8eeae3a 100644 --- a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/round/QbftRoundIntegrationTest.java +++ b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/round/QbftRoundIntegrationTest.java @@ -55,6 +55,7 @@ import org.hyperledger.besu.util.Subscribers; import java.math.BigInteger; +import java.util.Optional; import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.BeforeEach; @@ -112,7 +113,7 @@ public void setup() { headerTestFixture.extraData(qbftExtraDataEncoder.encode(proposedExtraData)); headerTestFixture.number(1); final BlockHeader header = headerTestFixture.buildHeader(); - proposedBlock = new Block(header, new BlockBody(emptyList(), emptyList())); + proposedBlock = new Block(header, new BlockBody(emptyList(), emptyList(), Optional.empty())); when(blockImporter.importBlock(any(), any(), any())).thenReturn(new BlockImportResult(true)); diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/blockcreation/PkiQbftBlockCreatorTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/blockcreation/PkiQbftBlockCreatorTest.java index a36da3758f1..9fd36da23b3 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/blockcreation/PkiQbftBlockCreatorTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/blockcreation/PkiQbftBlockCreatorTest.java @@ -40,6 +40,7 @@ import org.hyperledger.besu.pki.cms.CmsCreator; import java.util.Collections; +import java.util.Optional; import org.apache.tuweni.bytes.Bytes; import org.junit.Before; @@ -121,7 +122,7 @@ private Block createBlockBeingProposed() { final Block block = new Block( blockHeaderWithExtraData, - new BlockBody(Collections.emptyList(), Collections.emptyList())); + new BlockBody(Collections.emptyList(), Collections.emptyList(), Optional.empty())); when(blockCreator.createBlock(eq(1L))) .thenReturn(new BlockCreationResult(block, new TransactionSelectionResults())); diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/messagewrappers/ProposalTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/messagewrappers/ProposalTest.java index 14dde662d57..88d1d1a1059 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/messagewrappers/ProposalTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/messagewrappers/ProposalTest.java @@ -51,7 +51,7 @@ public class ProposalTest { private static final Block BLOCK = new Block( new BlockHeaderTestFixture().extraData(bftExtraDataCodec.encode(extraData)).buildHeader(), - new BlockBody(Collections.emptyList(), Collections.emptyList())); + new BlockBody(Collections.emptyList(), Collections.emptyList(), Optional.empty())); @Test public void canRoundTripProposalMessage() { diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/messagewrappers/RoundChangeTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/messagewrappers/RoundChangeTest.java index 34c430801a4..24ed3ebccd5 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/messagewrappers/RoundChangeTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/messagewrappers/RoundChangeTest.java @@ -51,7 +51,7 @@ public class RoundChangeTest { new BlockHeaderTestFixture() .extraData(new QbftExtraDataCodec().encode(extraData)) .buildHeader(), - new BlockBody(Collections.emptyList(), Collections.emptyList())); + new BlockBody(Collections.emptyList(), Collections.emptyList(), Optional.empty())); @Test public void canRoundTripARoundChangeMessage() { diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManagerTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManagerTest.java index 295703209fb..1a8af770bf1 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManagerTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManagerTest.java @@ -123,7 +123,7 @@ private void buildCreatedBlock() { headerTestFixture.extraData(bftExtraDataCodec.encode(extraData)); final BlockHeader header = headerTestFixture.buildHeader(); - createdBlock = new Block(header, new BlockBody(emptyList(), emptyList())); + createdBlock = new Block(header, new BlockBody(emptyList(), emptyList(), Optional.empty())); } @Before diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundTest.java index e17947b0d56..c4080c33672 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundTest.java @@ -127,7 +127,7 @@ public void setup() { headerTestFixture.number(1); final BlockHeader header = headerTestFixture.buildHeader(); - proposedBlock = new Block(header, new BlockBody(emptyList(), emptyList())); + proposedBlock = new Block(header, new BlockBody(emptyList(), emptyList(), Optional.empty())); when(blockCreator.createBlock(anyLong())) .thenReturn(new BlockCreationResult(proposedBlock, new TransactionSelectionResults())); diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ForkingValidatorProviderTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ForkingValidatorProviderTest.java index d44af1b6151..65510d833d4 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ForkingValidatorProviderTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ForkingValidatorProviderTest.java @@ -92,7 +92,8 @@ public void setup() { private Block createEmptyBlock(final long blockNumber, final Hash parentHash) { headerBuilder.number(blockNumber).parentHash(parentHash).coinbase(AddressHelpers.ofValue(0)); - return new Block(headerBuilder.buildHeader(), new BlockBody(emptyList(), emptyList())); + return new Block( + headerBuilder.buildHeader(), new BlockBody(emptyList(), emptyList(), Optional.empty())); } @Test diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/TransactionValidatorProviderTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/TransactionValidatorProviderTest.java index 8844cd6d90e..adcb951e98f 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/TransactionValidatorProviderTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/TransactionValidatorProviderTest.java @@ -35,6 +35,7 @@ import java.util.Collection; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import com.google.common.collect.Lists; @@ -77,7 +78,8 @@ public void setup() { private Block createEmptyBlock(final long blockNumber, final Hash parentHash) { headerBuilder.number(blockNumber).parentHash(parentHash).coinbase(AddressHelpers.ofValue(0)); - return new Block(headerBuilder.buildHeader(), new BlockBody(emptyList(), emptyList())); + return new Block( + headerBuilder.buildHeader(), new BlockBody(emptyList(), emptyList(), Optional.empty())); } @Test diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/GWei.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/GWei.java new file mode 100644 index 00000000000..06e3667690d --- /dev/null +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/GWei.java @@ -0,0 +1,136 @@ +/* + * Copyright contributors to Hyperledger Besu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.datatypes; + +import org.hyperledger.besu.plugin.data.Quantity; + +import java.math.BigInteger; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.units.bigints.BaseUInt64Value; +import org.apache.tuweni.units.bigints.UInt64; + +/** A particular quantity of GWei, the Ethereum currency. */ +public final class GWei extends BaseUInt64Value implements Quantity { + + /** The constant ZERO. */ + public static final GWei ZERO = of(0); + + /** The constant ONE. */ + public static final GWei ONE = of(1); + + /** The constant MAX_GWEI. */ + public static final GWei MAX_GWEI = of(UInt64.MAX_VALUE); + + /** + * Instantiates a new GWei. + * + * @param value the value + */ + GWei(final UInt64 value) { + super(value, GWei::new); + } + + private GWei(final long v) { + this(UInt64.valueOf(v)); + } + + private GWei(final BigInteger v) { + this(UInt64.valueOf(v)); + } + + private GWei(final String hexString) { + this(UInt64.fromHexString(hexString)); + } + + /** + * Returns GWei of value. + * + * @param value the value + * @return the GWei + */ + public static GWei of(final long value) { + return new GWei(value); + } + + /** + * Returns GWei of BigInteger value. + * + * @param value the value + * @return the GWei + */ + public static GWei of(final BigInteger value) { + return new GWei(value); + } + + /** + * Returns GWei of UInt64 value. + * + * @param value the value + * @return the GWei + */ + public static GWei of(final UInt64 value) { + return new GWei(value); + } + + /** + * Wrap Bytes into GWei. + * + * @param value the value + * @return the g wei + */ + public static GWei wrap(final Bytes value) { + return new GWei(UInt64.fromBytes(value)); + } + + /** + * From hex string to GWei. + * + * @param str the hex string + * @return the GWei + */ + public static GWei fromHexString(final String str) { + return new GWei(str); + } + + /** + * Convert GWei to Wei + * + * @return Wei + */ + public Wei getAsWei() { + return Wei.of(getAsBigInteger().multiply(BigInteger.TEN.pow(9))); + } + + @Override + public Number getValue() { + return getAsBigInteger(); + } + + @Override + public BigInteger getAsBigInteger() { + return toBigInteger(); + } + + @Override + public String toHexString() { + return super.toHexString(); + } + + @Override + public String toShortHexString() { + return super.isZero() ? "0x0" : super.toShortHexString(); + } +} diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcResponseKey.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcResponseKey.java index ddbab7e15a7..a3f2cc59f85 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcResponseKey.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcResponseKey.java @@ -36,5 +36,7 @@ public enum JsonRpcResponseKey { TIMESTAMP, TOTAL_DIFFICULTY, TRANSACTION_ROOT, - BASEFEE + BASEFEE, + + WITHDRAWLS_ROOT } diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcResponseUtils.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcResponseUtils.java index 5c285f99d5b..17bc8f7d8c0 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcResponseUtils.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcResponseUtils.java @@ -32,6 +32,7 @@ import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcResponseKey.TIMESTAMP; import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcResponseKey.TOTAL_DIFFICULTY; import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcResponseKey.TRANSACTION_ROOT; +import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcResponseKey.WITHDRAWLS_ROOT; import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; import org.hyperledger.besu.datatypes.Address; @@ -96,6 +97,7 @@ public JsonRpcResponse response( values.containsKey(BASEFEE) ? Wei.of(unsignedInt256(values.get(BASEFEE))) : null; final Difficulty totalDifficulty = Difficulty.of(unsignedInt256(values.get(TOTAL_DIFFICULTY))); final int size = unsignedInt(values.get(SIZE)); + final Hash withdrawalsRoot = hash(values.get(WITHDRAWLS_ROOT)); final List ommers = new ArrayList<>(); @@ -117,6 +119,7 @@ public JsonRpcResponse response( baseFee, mixHash, nonce, + withdrawalsRoot, blockHeaderFunctions); return new JsonRpcSuccessResponse( diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthGetFilterChangesIntegrationTest.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthGetFilterChangesIntegrationTest.java index 333720ac4cc..c61dcdbe515 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthGetFilterChangesIntegrationTest.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthGetFilterChangesIntegrationTest.java @@ -283,7 +283,7 @@ private Block appendBlock( .parentHash(parentBlock.getHash()) .number(parentBlock.getNumber() + 1) .buildHeader(), - new BlockBody(transactionList, emptyList())); + new BlockBody(transactionList, emptyList(), Optional.empty())); final List transactionReceipts = transactionList.stream() .map(transaction -> new TransactionReceipt(1, 1, emptyList(), Optional.empty())) diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthGetFilterChangesIntegrationTest.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthGetFilterChangesIntegrationTest.java index 703bd22ef0c..857a29a25dc 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthGetFilterChangesIntegrationTest.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthGetFilterChangesIntegrationTest.java @@ -283,7 +283,7 @@ private Block appendBlock( .parentHash(parentBlock.getHash()) .number(parentBlock.getNumber() + 1) .buildHeader(), - new BlockBody(transactionList, emptyList())); + new BlockBody(transactionList, emptyList(), Optional.empty())); final List transactionReceipts = transactionList.stream() .map(transaction -> new TransactionReceipt(1, 1, emptyList(), Optional.empty())) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java index 130955c7fb3..2daee2998c5 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java @@ -47,9 +47,13 @@ public enum RpcMethod { DEBUG_GET_BAD_BLOCKS("debug_getBadBlocks"), ENGINE_GET_PAYLOAD("engine_getPayloadV1"), + ENGINE_GET_PAYLOAD_V2("engine_getPayloadV2"), ENGINE_EXECUTE_PAYLOAD("engine_executePayloadV1"), ENGINE_NEW_PAYLOAD("engine_newPayloadV1"), + ENGINE_NEW_PAYLOAD_V2("engine_newPayloadV2"), + ENGINE_FORKCHOICE_UPDATED("engine_forkchoiceUpdatedV1"), + ENGINE_FORKCHOICE_UPDATED_V2("engine_forkchoiceUpdatedV2"), ENGINE_EXCHANGE_TRANSITION_CONFIGURATION("engine_exchangeTransitionConfigurationV1"), GOQUORUM_ETH_GET_QUORUM_PAYLOAD("eth_getQuorumPayload"), diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdated.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdated.java index 64c427da1e8..8af0c8f005f 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdated.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdated.java @@ -18,10 +18,10 @@ import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.SYNCING; import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.VALID; import static org.hyperledger.besu.util.Slf4jLambdaHelper.debugLambda; +import static org.hyperledger.besu.util.Slf4jLambdaHelper.warnLambda; import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator; import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator.ForkchoiceResult; -import org.hyperledger.besu.consensus.merge.blockcreation.PayloadAttributes; import org.hyperledger.besu.consensus.merge.blockcreation.PayloadIdentifier; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ProtocolContext; @@ -132,15 +132,21 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) ForkchoiceResult result = mergeCoordinator.updateForkChoice( - newHead, - forkChoice.getFinalizedBlockHash(), - forkChoice.getSafeBlockHash(), - maybePayloadAttributes.map( - payloadAttributes -> - new PayloadAttributes( - payloadAttributes.getTimestamp(), - payloadAttributes.getPrevRandao(), - payloadAttributes.getSuggestedFeeRecipient()))); + newHead, forkChoice.getFinalizedBlockHash(), forkChoice.getSafeBlockHash()); + + if (maybePayloadAttributes.isPresent()) { + + if (!isPayloadAttributesValid(maybePayloadAttributes.get(), newHead)) { + warnLambda( + LOG, + "Invalid payload attributes: {}", + () -> + maybePayloadAttributes + .map(EnginePayloadAttributesParameter::serialize) + .orElse(null)); + return new JsonRpcErrorResponse(requestId, JsonRpcError.INVALID_PAYLOAD_ATTRIBUTES); + } + } if (!result.isValid()) { logForkchoiceUpdatedCall(INVALID, forkChoice); @@ -155,7 +161,8 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) newHead, payloadAttributes.getTimestamp(), payloadAttributes.getPrevRandao(), - payloadAttributes.getSuggestedFeeRecipient())); + payloadAttributes.getSuggestedFeeRecipient(), + Optional.empty())); payloadId.ifPresent( pid -> @@ -175,6 +182,11 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) Optional.empty())); } + private boolean isPayloadAttributesValid( + final EnginePayloadAttributesParameter payloadAttributes, final BlockHeader headBlockHeader) { + return payloadAttributes.getTimestamp() > headBlockHeader.getTimestamp(); + } + private JsonRpcResponse handleNonValidForkchoiceUpdate( final Object requestId, final ForkchoiceResult result) { JsonRpcResponse response; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV2.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV2.java new file mode 100644 index 00000000000..976d8f21f52 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV2.java @@ -0,0 +1,354 @@ +/* + * Copyright Hyperledger 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.api.jsonrpc.internal.methods.engine; + +import static java.util.stream.Collectors.toList; +import static org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator.ForkchoiceResult.Status.INVALID_PAYLOAD_ATTRIBUTES; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.INVALID; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.SYNCING; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.VALID; +import static org.hyperledger.besu.util.Slf4jLambdaHelper.debugLambda; +import static org.hyperledger.besu.util.Slf4jLambdaHelper.warnLambda; + +import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator; +import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator.ForkchoiceResult; +import org.hyperledger.besu.consensus.merge.blockcreation.PayloadIdentifier; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EngineForkchoiceUpdatedParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EnginePayloadAttributesParameterV2; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineUpdateForkchoiceResult; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.Withdrawal; +import org.hyperledger.besu.ethereum.mainnet.TimestampSchedule; +import org.hyperledger.besu.ethereum.mainnet.WithdrawalsValidator; + +import java.util.List; +import java.util.Optional; +import java.util.function.BiConsumer; + +import io.vertx.core.Vertx; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class EngineForkchoiceUpdatedV2 extends ExecutionEngineJsonRpcMethod { + private static final Logger LOG = LoggerFactory.getLogger(EngineForkchoiceUpdatedV2.class); + private final TimestampSchedule timestampSchedule; + private final MergeMiningCoordinator mergeCoordinator; + + public EngineForkchoiceUpdatedV2( + final Vertx vertx, + final TimestampSchedule timestampSchedule, + final ProtocolContext protocolContext, + final MergeMiningCoordinator mergeCoordinator, + final EngineCallListener engineCallListener) { + super(vertx, protocolContext, engineCallListener); + this.timestampSchedule = timestampSchedule; + this.mergeCoordinator = mergeCoordinator; + } + + @Override + public String getName() { + return RpcMethod.ENGINE_FORKCHOICE_UPDATED_V2.getMethodName(); + } + + @Override + public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) { + engineCallListener.executionEngineCalled(); + + final Object requestId = requestContext.getRequest().getId(); + + final EngineForkchoiceUpdatedParameter forkChoice = + requestContext.getRequiredParameter(0, EngineForkchoiceUpdatedParameter.class); + final Optional maybePayloadAttributes = + requestContext.getOptionalParameter(1, EnginePayloadAttributesParameterV2.class); + + LOG.debug("Forkchoice parameters {}", forkChoice); + + mergeContext + .get() + .fireNewUnverifiedForkchoiceEvent( + forkChoice.getHeadBlockHash(), + forkChoice.getSafeBlockHash(), + forkChoice.getFinalizedBlockHash()); + + if (mergeContext.get().isSyncing()) { + return syncingResponse(requestId, forkChoice); + } + + if (mergeCoordinator.isBadBlock(forkChoice.getHeadBlockHash())) { + logForkchoiceUpdatedCall(INVALID, forkChoice); + return new JsonRpcSuccessResponse( + requestId, + new EngineUpdateForkchoiceResult( + INVALID, + mergeCoordinator + .getLatestValidHashOfBadBlock(forkChoice.getHeadBlockHash()) + .orElse(Hash.ZERO), + null, + Optional.of(forkChoice.getHeadBlockHash() + " is an invalid block"))); + } + + final Optional maybeNewHead = + mergeCoordinator.getOrSyncHeadByHash( + forkChoice.getHeadBlockHash(), forkChoice.getFinalizedBlockHash()); + + if (maybeNewHead.isEmpty()) { + return syncingResponse(requestId, forkChoice); + } + + final BlockHeader newHead = maybeNewHead.get(); + + maybePayloadAttributes.ifPresentOrElse( + this::logPayload, () -> LOG.debug("Payload attributes are null")); + + if (!isValidForkchoiceState( + forkChoice.getSafeBlockHash(), forkChoice.getFinalizedBlockHash(), newHead)) { + logForkchoiceUpdatedCall(INVALID, forkChoice); + return new JsonRpcErrorResponse(requestId, JsonRpcError.INVALID_FORKCHOICE_STATE); + } + + ForkchoiceResult result = + mergeCoordinator.updateForkChoice( + newHead, forkChoice.getFinalizedBlockHash(), forkChoice.getSafeBlockHash()); + + if (maybePayloadAttributes.isPresent()) { + EnginePayloadAttributesParameterV2 enginePayloadAttributesParameterV2 = + maybePayloadAttributes.get(); + List withdrawals = + Optional.ofNullable(enginePayloadAttributesParameterV2.getWithdrawals()) + .map(ws -> ws.stream().map(WithdrawalParameter::toWithdrawal).collect(toList())) + .orElse(null); + + Boolean isWithdrawalsValid = + timestampSchedule + .getByTimestamp(enginePayloadAttributesParameterV2.getTimestamp()) + .map( + protocolSpec -> + protocolSpec.getWithdrawalsValidator().validateWithdrawals(withdrawals)) + // TODO Withdrawals this is a quirk of the fact timestampSchedule doesn't fallback to + // the + // previous fork. This might be resolved when + // https://github.com/hyperledger/besu/issues/4789 is played + // and if we can combine protocolSchedule and timestampSchedule. + .orElseGet( + () -> + new WithdrawalsValidator.ProhibitedWithdrawals() + .validateWithdrawals(withdrawals)); + if (!isPayloadAttributesValid(enginePayloadAttributesParameterV2, newHead) + || !isWithdrawalsValid) { + warnLambda( + LOG, + "Invalid payload attributes: {}", + () -> + maybePayloadAttributes + .map(EnginePayloadAttributesParameterV2::serialize) + .orElse(null)); + return new JsonRpcErrorResponse(requestId, JsonRpcError.INVALID_PARAMS); + } + } + + if (!result.isValid()) { + logForkchoiceUpdatedCall(INVALID, forkChoice); + return handleNonValidForkchoiceUpdate(requestId, result); + } + + // begin preparing a block if we have a non-empty payload attributes param + Optional payloadId = + maybePayloadAttributes.map( + payloadAttributes -> + mergeCoordinator.preparePayload( + newHead, + payloadAttributes.getTimestamp(), + payloadAttributes.getPrevRandao(), + payloadAttributes.getSuggestedFeeRecipient(), + Optional.ofNullable(payloadAttributes.getWithdrawals()) + .map( + w -> + w.stream() + .map(WithdrawalParameter::toWithdrawal) + .collect(toList())))); + + payloadId.ifPresent( + pid -> + debugLambda( + LOG, + "returning identifier {} for requested payload {}", + pid::toHexString, + () -> maybePayloadAttributes.map(EnginePayloadAttributesParameterV2::serialize))); + + logForkchoiceUpdatedCall(VALID, forkChoice); + return new JsonRpcSuccessResponse( + requestId, + new EngineUpdateForkchoiceResult( + VALID, + result.getNewHead().map(BlockHeader::getHash).orElse(null), + payloadId.orElse(null), + Optional.empty())); + } + + private boolean isPayloadAttributesValid( + final EnginePayloadAttributesParameterV2 payloadAttributes, + final BlockHeader headBlockHeader) { + return payloadAttributes.getTimestamp() > headBlockHeader.getTimestamp(); + } + + private JsonRpcResponse handleNonValidForkchoiceUpdate( + final Object requestId, final ForkchoiceResult result) { + JsonRpcResponse response; + + final Optional latestValid = result.getLatestValid(); + + switch (result.getStatus()) { + case INVALID: + response = + new JsonRpcSuccessResponse( + requestId, + new EngineUpdateForkchoiceResult( + INVALID, latestValid.orElse(null), null, result.getErrorMessage())); + break; + case INVALID_PAYLOAD_ATTRIBUTES: + response = new JsonRpcErrorResponse(requestId, JsonRpcError.INVALID_PAYLOAD_ATTRIBUTES); + break; + case IGNORE_UPDATE_TO_OLD_HEAD: + response = + new JsonRpcSuccessResponse( + requestId, + new EngineUpdateForkchoiceResult( + VALID, latestValid.orElse(null), null, result.getErrorMessage())); + break; + default: + throw new AssertionError( + "ForkchoiceResult.Status " + + result.getStatus() + + " not handled in EngineForkchoiceUpdated.handleForkchoiceError"); + } + + return response; + } + + private void logPayload(final EnginePayloadAttributesParameterV2 payloadAttributes) { + debugLambda( + LOG, + "timestamp: {}, prevRandao: {}, suggestedFeeRecipient: {}, withdrawals: {}", + payloadAttributes::getTimestamp, + () -> payloadAttributes.getPrevRandao().toHexString(), + () -> payloadAttributes.getSuggestedFeeRecipient().toHexString(), + payloadAttributes::getWithdrawals); + } + + private boolean isValidForkchoiceState( + final Hash safeBlockHash, final Hash finalizedBlockHash, final BlockHeader newBlock) { + Optional maybeFinalizedBlock = Optional.empty(); + + if (!finalizedBlockHash.isZero()) { + maybeFinalizedBlock = protocolContext.getBlockchain().getBlockHeader(finalizedBlockHash); + + // if the finalized block hash is not zero, we always need to have its block, because we + // only do this check once we have finished syncing + if (maybeFinalizedBlock.isEmpty()) { + return false; + } + + // a valid finalized block must be an ancestor of the new head + if (!mergeCoordinator.isDescendantOf(maybeFinalizedBlock.get(), newBlock)) { + return false; + } + } + + // A zero value is only allowed, if the transition block is not yet finalized. + // Once we have at least one finalized block, the transition block has either been finalized + // directly + // or through one of its descendants. + if (safeBlockHash.isZero()) { + return finalizedBlockHash.isZero(); + } + + final Optional maybeSafeBlock = + protocolContext.getBlockchain().getBlockHeader(safeBlockHash); + + // if the safe block hash is not zero, we always need to have its block, because we + // only do this check once we have finished syncing + if (maybeSafeBlock.isEmpty()) { + return false; + } + + // a valid safe block must be a descendant of the finalized block + if (maybeFinalizedBlock.isPresent() + && !mergeCoordinator.isDescendantOf(maybeFinalizedBlock.get(), maybeSafeBlock.get())) { + return false; + } + + // a valid safe block must be an ancestor of the new block + return mergeCoordinator.isDescendantOf(maybeSafeBlock.get(), newBlock); + } + + private JsonRpcResponse syncingResponse( + final Object requestId, final EngineForkchoiceUpdatedParameter forkChoice) { + + logForkchoiceUpdatedCall(this::logAtDebug, SYNCING, forkChoice); + return new JsonRpcSuccessResponse( + requestId, new EngineUpdateForkchoiceResult(SYNCING, null, null, Optional.empty())); + } + + // fcU calls are synchronous, no need to make volatile + private long lastFcuInfoLog = System.currentTimeMillis(); + private static final String logMessage = + "{} for fork-choice-update: head: {}, finalized: {}, safeBlockHash: {}"; + + private void logForkchoiceUpdatedCall( + final EngineStatus status, final EngineForkchoiceUpdatedParameter forkChoice) { + logForkchoiceUpdatedCall(this::logAtInfo, status, forkChoice); + } + + private void logForkchoiceUpdatedCall( + final BiConsumer logAtLevel, + final EngineStatus status, + final EngineForkchoiceUpdatedParameter forkChoice) { + // cheaply limit the noise of fcU during consensus client syncing to once a minute: + if (lastFcuInfoLog + ENGINE_API_LOGGING_THRESHOLD < System.currentTimeMillis()) { + lastFcuInfoLog = System.currentTimeMillis(); + logAtLevel.accept(status, forkChoice); + } + } + + private void logAtInfo( + final EngineStatus status, final EngineForkchoiceUpdatedParameter forkChoice) { + LOG.info( + logMessage, + status.name(), + forkChoice.getHeadBlockHash(), + forkChoice.getFinalizedBlockHash(), + forkChoice.getSafeBlockHash()); + } + + private void logAtDebug( + final EngineStatus status, final EngineForkchoiceUpdatedParameter forkChoice) { + LOG.debug( + logMessage, + status.name(), + forkChoice.getHeadBlockHash(), + forkChoice.getFinalizedBlockHash(), + forkChoice.getSafeBlockHash()); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV2.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV2.java new file mode 100644 index 00000000000..cbd295b02fd --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV2.java @@ -0,0 +1,88 @@ +/* + * Copyright Hyperledger 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.api.jsonrpc.internal.methods.engine; + +import static org.hyperledger.besu.util.Slf4jLambdaHelper.debugLambda; +import static org.hyperledger.besu.util.Slf4jLambdaHelper.infoLambda; + +import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator; +import org.hyperledger.besu.consensus.merge.blockcreation.PayloadIdentifier; +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory; +import org.hyperledger.besu.ethereum.core.Block; + +import java.util.Optional; + +import io.vertx.core.Vertx; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class EngineGetPayloadV2 extends ExecutionEngineJsonRpcMethod { + + private final MergeMiningCoordinator mergeMiningCoordinator; + private final BlockResultFactory blockResultFactory; + private static final Logger LOG = LoggerFactory.getLogger(EngineGetPayloadV2.class); + + public EngineGetPayloadV2( + final Vertx vertx, + final ProtocolContext protocolContext, + final MergeMiningCoordinator mergeMiningCoordinator, + final BlockResultFactory blockResultFactory, + final EngineCallListener engineCallListener) { + super(vertx, protocolContext, engineCallListener); + this.mergeMiningCoordinator = mergeMiningCoordinator; + this.blockResultFactory = blockResultFactory; + } + + @Override + public String getName() { + return RpcMethod.ENGINE_GET_PAYLOAD_V2.getMethodName(); + } + + @Override + public JsonRpcResponse syncResponse(final JsonRpcRequestContext request) { + engineCallListener.executionEngineCalled(); + + final PayloadIdentifier payloadId = request.getRequiredParameter(0, PayloadIdentifier.class); + mergeMiningCoordinator.finalizeProposalById(payloadId); + final Optional block = mergeContext.get().retrieveBlockById(payloadId); + if (block.isPresent()) { + var proposal = block.get(); + var proposalHeader = proposal.getHeader(); + var maybeWithdrawals = proposal.getBody().getWithdrawals(); + infoLambda( + LOG, + "Fetch block proposal by identifier: {}, hash: {}, number: {}, coinbase: {}, transaction count: {}, withdrawals count: {}", + () -> payloadId.toHexString(), + () -> proposalHeader.getHash(), + () -> proposalHeader.getNumber(), + () -> proposalHeader.getCoinbase(), + () -> proposal.getBody().getTransactions().size(), + () -> maybeWithdrawals.isEmpty() ? "No withdraws" : maybeWithdrawals.get().size()); + debugLambda(LOG, "assembledBlock {}", () -> block.map(Block::toString).get()); + return new JsonRpcSuccessResponse( + request.getRequest().getId(), + blockResultFactory.enginePayloadTransactionCompleteV2(block.get())); + } + return new JsonRpcErrorResponse(request.getRequest().getId(), JsonRpcError.UNKNOWN_PAYLOAD); + } +} 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 3b495b10135..3c36c581d70 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 @@ -144,6 +144,7 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) blockParam.getBaseFeePerGas(), blockParam.getPrevRandao(), 0, + Hash.EMPTY, headerFunctions); // ensure the block hash matches the blockParam hash @@ -183,9 +184,9 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) INVALID, "block timestamp not greater than parent"); } - final var block = - new Block(newBlockHeader, new BlockBody(transactions, Collections.emptyList())); + new Block( + newBlockHeader, new BlockBody(transactions, Collections.emptyList(), Optional.empty())); if (parentHeader.isEmpty()) { debugLambda( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV2.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV2.java new file mode 100644 index 00000000000..9e179d4c7ec --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV2.java @@ -0,0 +1,313 @@ +/* + * Copyright Hyperledger 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.api.jsonrpc.internal.methods.engine; + +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.ACCEPTED; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.INVALID; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.INVALID_BLOCK_HASH; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.SYNCING; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.VALID; +import static org.hyperledger.besu.util.Slf4jLambdaHelper.debugLambda; +import static org.hyperledger.besu.util.Slf4jLambdaHelper.traceLambda; + +import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.BlockProcessingResult; +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EnginePayloadParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EnginePayloadStatusResult; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockBody; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; +import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.Withdrawal; +import org.hyperledger.besu.ethereum.core.encoding.TransactionDecoder; +import org.hyperledger.besu.ethereum.eth.manager.EthPeers; +import org.hyperledger.besu.ethereum.mainnet.BodyValidation; +import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; +import org.hyperledger.besu.ethereum.rlp.RLPException; +import org.hyperledger.besu.plugin.services.exception.StorageException; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import io.vertx.core.Vertx; +import io.vertx.core.json.Json; +import org.apache.tuweni.bytes.Bytes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class EngineNewPayloadV2 extends ExecutionEngineJsonRpcMethod { + + private static final Hash OMMERS_HASH_CONSTANT = Hash.EMPTY_LIST_HASH; + private static final Logger LOG = LoggerFactory.getLogger(EngineNewPayloadV2.class); + private static final BlockHeaderFunctions headerFunctions = new MainnetBlockHeaderFunctions(); + private final MergeMiningCoordinator mergeCoordinator; + private final EthPeers ethPeers; + + public EngineNewPayloadV2( + final Vertx vertx, + final ProtocolContext protocolContext, + final MergeMiningCoordinator mergeCoordinator, + final EthPeers ethPeers, + final EngineCallListener engineCallListener) { + super(vertx, protocolContext, engineCallListener); + this.mergeCoordinator = mergeCoordinator; + this.ethPeers = ethPeers; + } + + @Override + public String getName() { + return RpcMethod.ENGINE_NEW_PAYLOAD_V2.getMethodName(); + } + + @Override + public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) { + engineCallListener.executionEngineCalled(); + + final EnginePayloadParameter blockParam = + requestContext.getRequiredParameter(0, EnginePayloadParameter.class); + + Object reqId = requestContext.getRequest().getId(); + + traceLambda(LOG, "blockparam: {}", () -> Json.encodePrettily(blockParam)); + + final List transactions; + try { + transactions = + blockParam.getTransactions().stream() + .map(Bytes::fromHexString) + .map(TransactionDecoder::decodeOpaqueBytes) + .collect(Collectors.toList()); + } catch (final RLPException | IllegalArgumentException e) { + return respondWithInvalid( + reqId, + blockParam, + mergeCoordinator.getLatestValidAncestor(blockParam.getParentHash()).orElse(null), + INVALID, + "Failed to decode transactions from block parameter"); + } + final Optional> maybeWithdrawals = + Optional.ofNullable(blockParam.getWithdrawals()) + .map( + w -> + w.stream().map(WithdrawalParameter::toWithdrawal).collect(Collectors.toList())); + + if (blockParam.getExtraData() == null) { + return respondWithInvalid( + reqId, + blockParam, + mergeCoordinator.getLatestValidAncestor(blockParam.getParentHash()).orElse(null), + INVALID, + "Field extraData must not be null"); + } + + final BlockHeader newBlockHeader = + new BlockHeader( + blockParam.getParentHash(), + OMMERS_HASH_CONSTANT, + blockParam.getFeeRecipient(), + blockParam.getStateRoot(), + BodyValidation.transactionsRoot(transactions), + blockParam.getReceiptsRoot(), + blockParam.getLogsBloom(), + Difficulty.ZERO, + blockParam.getBlockNumber(), + blockParam.getGasLimit(), + blockParam.getGasUsed(), + blockParam.getTimestamp(), + Bytes.fromHexString(blockParam.getExtraData()), + blockParam.getBaseFeePerGas(), + blockParam.getPrevRandao(), + 0, + // TODO Withdrawals - Hash.EMPTY distinguishes from Hash.EMPTY_TRIE_HASH + maybeWithdrawals.map(BodyValidation::withdrawalsRoot).orElse(Hash.EMPTY), + headerFunctions); + + // ensure the block hash matches the blockParam hash + // this must be done before any other check + if (!newBlockHeader.getHash().equals(blockParam.getBlockHash())) { + String errorMessage = + String.format( + "Computed block hash %s does not match block hash parameter %s", + newBlockHeader.getBlockHash(), blockParam.getBlockHash()); + LOG.debug(errorMessage); + return respondWithInvalid(reqId, blockParam, null, INVALID, errorMessage); + } + // do we already have this payload + if (protocolContext.getBlockchain().getBlockByHash(newBlockHeader.getBlockHash()).isPresent()) { + LOG.debug("block already present"); + return respondWith(reqId, blockParam, blockParam.getBlockHash(), VALID); + } + if (mergeCoordinator.isBadBlock(blockParam.getBlockHash())) { + return respondWithInvalid( + reqId, + blockParam, + mergeCoordinator + .getLatestValidHashOfBadBlock(blockParam.getBlockHash()) + .orElse(Hash.ZERO), + INVALID, + "Block already present in bad block manager."); + } + + Optional parentHeader = + protocolContext.getBlockchain().getBlockHeader(blockParam.getParentHash()); + if (parentHeader.isPresent() + && (blockParam.getTimestamp() <= parentHeader.get().getTimestamp())) { + return respondWithInvalid( + reqId, + blockParam, + mergeCoordinator.getLatestValidAncestor(blockParam.getParentHash()).orElse(null), + INVALID, + "block timestamp not greater than parent"); + } + + final var block = + new Block( + newBlockHeader, new BlockBody(transactions, Collections.emptyList(), maybeWithdrawals)); + final String warningMessage = "Sync to block " + block.toLogString() + " failed"; + + if (mergeContext.get().isSyncing() || parentHeader.isEmpty()) { + LOG.debug( + "isSyncing: {} parentHeaderMissing: {}, adding {} to backwardsync", + mergeContext.get().isSyncing(), + parentHeader.isEmpty(), + block.getHash()); + mergeCoordinator + .appendNewPayloadToSync(block) + .exceptionally( + exception -> { + LOG.warn(warningMessage, exception.getMessage()); + return null; + }); + return respondWith(reqId, blockParam, null, SYNCING); + } + + final var latestValidAncestor = mergeCoordinator.getLatestValidAncestor(newBlockHeader); + + if (latestValidAncestor.isEmpty()) { + return respondWith(reqId, blockParam, null, ACCEPTED); + } + + // execute block and return result response + final long startTimeMs = System.currentTimeMillis(); + final BlockProcessingResult executionResult = mergeCoordinator.rememberBlock(block); + + if (executionResult.errorMessage.isEmpty()) { + logImportedBlockInfo(block, (System.currentTimeMillis() - startTimeMs) / 1000.0); + return respondWith(reqId, blockParam, newBlockHeader.getHash(), VALID); + } else { + if (executionResult.cause.isPresent()) { + // TODO; would prefer to invert the logic so we rpc error on anything that isn't a + // consensus error + if (executionResult.cause.get() instanceof StorageException) { + JsonRpcError error = JsonRpcError.INTERNAL_ERROR; + JsonRpcErrorResponse response = new JsonRpcErrorResponse(reqId, error); + return response; + } + } + LOG.debug("New payload is invalid: {}", executionResult.errorMessage.get()); + return respondWithInvalid( + reqId, + blockParam, + latestValidAncestor.get(), + INVALID, + executionResult.errorMessage.get()); + } + } + + JsonRpcResponse respondWith( + final Object requestId, + final EnginePayloadParameter param, + final Hash latestValidHash, + final EngineStatus status) { + if (INVALID.equals(status) || INVALID_BLOCK_HASH.equals(status)) { + throw new IllegalArgumentException( + "Don't call respondWith() with invalid status of " + status.toString()); + } + debugLambda( + LOG, + "New payload: number: {}, hash: {}, parentHash: {}, latestValidHash: {}, status: {}", + () -> param.getBlockNumber(), + () -> param.getBlockHash(), + () -> param.getParentHash(), + () -> latestValidHash == null ? null : latestValidHash.toHexString(), + status::name); + return new JsonRpcSuccessResponse( + requestId, new EnginePayloadStatusResult(status, latestValidHash, Optional.empty())); + } + + // engine api calls are synchronous, no need for volatile + private long lastInvalidWarn = 0; + + JsonRpcResponse respondWithInvalid( + final Object requestId, + final EnginePayloadParameter param, + final Hash latestValidHash, + final EngineStatus invalidStatus, + final String validationError) { + if (!INVALID.equals(invalidStatus) && !INVALID_BLOCK_HASH.equals(invalidStatus)) { + throw new IllegalArgumentException( + "Don't call respondWithInvalid() with non-invalid status of " + invalidStatus.toString()); + } + final String invalidBlockLogMessage = + String.format( + "Invalid new payload: number: %s, hash: %s, parentHash: %s, latestValidHash: %s, status: %s, validationError: %s", + param.getBlockNumber(), + param.getBlockHash(), + param.getParentHash(), + latestValidHash == null ? null : latestValidHash.toHexString(), + invalidStatus.name(), + validationError); + // always log invalid at DEBUG + LOG.debug(invalidBlockLogMessage); + // periodically log at WARN + if (lastInvalidWarn + ENGINE_API_LOGGING_THRESHOLD < System.currentTimeMillis()) { + lastInvalidWarn = System.currentTimeMillis(); + LOG.warn(invalidBlockLogMessage); + } + return new JsonRpcSuccessResponse( + requestId, + new EnginePayloadStatusResult( + invalidStatus, latestValidHash, Optional.of(validationError))); + } + + private void logImportedBlockInfo(final Block block, final double timeInS) { + LOG.info( + String.format( + "Imported #%,d / %d tx / base fee %s / %,d (%01.1f%%) gas / (%s) in %01.3fs. Peers: %d", + block.getHeader().getNumber(), + block.getBody().getTransactions().size(), + block.getHeader().getBaseFee().map(Wei::toHumanReadableString).orElse("N/A"), + block.getHeader().getGasUsed(), + (block.getHeader().getGasUsed() * 100.0) / block.getHeader().getGasLimit(), + block.getHash().toHexString(), + timeInS, + ethPeers.peerCount())); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EngineForkchoiceUpdatedParameter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EngineForkchoiceUpdatedParameter.java index 7b3e50a07e1..6d8dfdf3034 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EngineForkchoiceUpdatedParameter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EngineForkchoiceUpdatedParameter.java @@ -17,8 +17,10 @@ import org.hyperledger.besu.datatypes.Hash; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +@JsonIgnoreProperties(ignoreUnknown = true) public class EngineForkchoiceUpdatedParameter { private final Hash headBlockHash; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadAttributesParameter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadAttributesParameter.java index e9c2379c044..fd7a0cedabe 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadAttributesParameter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadAttributesParameter.java @@ -17,10 +17,12 @@ import org.hyperledger.besu.datatypes.Address; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import io.vertx.core.json.JsonObject; import org.apache.tuweni.bytes.Bytes32; +@JsonIgnoreProperties(ignoreUnknown = true) public class EnginePayloadAttributesParameter { final Long timestamp; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadAttributesParameterV2.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadAttributesParameterV2.java new file mode 100644 index 00000000000..ad7fee7bccf --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadAttributesParameterV2.java @@ -0,0 +1,78 @@ +/* + * Copyright Hyperledger 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.api.jsonrpc.internal.parameters; + +import static java.util.stream.Collectors.toList; + +import org.hyperledger.besu.datatypes.Address; + +import java.util.List; +import java.util.Optional; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.vertx.core.json.JsonObject; +import org.apache.tuweni.bytes.Bytes32; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class EnginePayloadAttributesParameterV2 { + + final Long timestamp; + final Bytes32 prevRandao; + final Address suggestedFeeRecipient; + final List withdrawals; + + @JsonCreator + public EnginePayloadAttributesParameterV2( + @JsonProperty("timestamp") final String timestamp, + @JsonProperty("prevRandao") final String prevRandao, + @JsonProperty("suggestedFeeRecipient") final String suggestedFeeRecipient, + @JsonProperty("withdrawals") final List withdrawals) { + this.timestamp = Long.decode(timestamp); + this.prevRandao = Bytes32.fromHexString(prevRandao); + this.suggestedFeeRecipient = Address.fromHexString(suggestedFeeRecipient); + this.withdrawals = withdrawals; + } + + public Long getTimestamp() { + return timestamp; + } + + public Bytes32 getPrevRandao() { + return prevRandao; + } + + public Address getSuggestedFeeRecipient() { + return suggestedFeeRecipient; + } + + public List getWithdrawals() { + return withdrawals; + } + + public String serialize() { + return new JsonObject() + .put("timestamp", timestamp) + .put("prevRandao", prevRandao.toHexString()) + .put("suggestedFeeRecipient", suggestedFeeRecipient.toHexString()) + .put( + "withdrawals", + Optional.ofNullable(withdrawals) + .map(w -> w.stream().map(WithdrawalParameter::asJsonObject).collect(toList())) + .orElse(null)) + .encode(); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadParameter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadParameter.java index 15f9b5d0748..d6e5923b539 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadParameter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadParameter.java @@ -46,6 +46,7 @@ public class EnginePayloadParameter { private final Hash receiptsRoot; private final LogsBloomFilter logsBloom; private final List transactions; + private final List withdrawals; @JsonCreator public EnginePayloadParameter( @@ -62,7 +63,8 @@ public EnginePayloadParameter( @JsonProperty("receiptRoot") final Hash receiptsRoot, @JsonProperty("logsBloom") final LogsBloomFilter logsBloom, @JsonProperty("prevRandao") final String prevRandao, - @JsonProperty("transactions") final List transactions) { + @JsonProperty("transactions") final List transactions, + @JsonProperty("withdrawals") final List withdrawals) { this.blockHash = blockHash; this.parentHash = parentHash; this.feeRecipient = feeRecipient; @@ -77,6 +79,7 @@ public EnginePayloadParameter( this.logsBloom = logsBloom; this.prevRandao = Bytes32.fromHexString(prevRandao); this.transactions = transactions; + this.withdrawals = withdrawals; } public Hash getBlockHash() { @@ -134,4 +137,8 @@ public Bytes32 getPrevRandao() { public List getTransactions() { return transactions; } + + public List getWithdrawals() { + return withdrawals; + } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/WithdrawalParameter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/WithdrawalParameter.java new file mode 100644 index 00000000000..cbc9cf521db --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/WithdrawalParameter.java @@ -0,0 +1,107 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.GWei; +import org.hyperledger.besu.ethereum.core.Withdrawal; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.vertx.core.json.JsonObject; +import org.apache.tuweni.units.bigints.UInt64; + +public class WithdrawalParameter { + + private final String index; + private final String validatorIndex; + private final String address; + private final String amount; + + @JsonCreator + public WithdrawalParameter( + @JsonProperty("index") final String index, + @JsonProperty("validatorIndex") final String validatorIndex, + @JsonProperty("address") final String address, + @JsonProperty("amount") final String amount) { + this.index = index; + this.validatorIndex = validatorIndex; + this.address = address; + this.amount = amount; + } + + public Withdrawal toWithdrawal() { + return new Withdrawal( + UInt64.fromHexString(index), + UInt64.fromHexString(validatorIndex), + Address.fromHexString(address), + GWei.fromHexString(amount)); + } + + public static WithdrawalParameter fromWithdrawal(final Withdrawal withdrawal) { + return new WithdrawalParameter( + withdrawal.getIndex().toBytes().toQuantityHexString(), + withdrawal.getValidatorIndex().toBytes().toQuantityHexString(), + withdrawal.getAddress().toString(), + withdrawal.getAmount().toShortHexString()); + } + + public JsonObject asJsonObject() { + return new JsonObject() + .put("index", index) + .put("validatorIndex", validatorIndex) + .put("address", address) + .put("amount", amount); + } + + @JsonGetter + public String getIndex() { + return index; + } + + @JsonGetter + public String getValidatorIndex() { + return validatorIndex; + } + + @JsonGetter + public String getAddress() { + return address; + } + + @JsonGetter + public String getAmount() { + return amount; + } + + @Override + public String toString() { + return "WithdrawalParameter{" + + "index='" + + index + + '\'' + + ", validatorIndex='" + + validatorIndex + + '\'' + + ", address='" + + address + + '\'' + + ", amount='" + + amount + + '\'' + + '}'; + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockReplay.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockReplay.java index 6f47b451a5e..04f2167b7a4 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockReplay.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockReplay.java @@ -85,7 +85,7 @@ public Optional beforeTransactionInBlock( action.performAction( transaction, header, blockchain, mutableWorldState, transactionProcessor)); } else { - final ProtocolSpec spec = protocolSchedule.getByBlockNumber(header.getNumber()); + final ProtocolSpec spec = protocolSchedule.getByBlockHeader(header); transactionProcessor.processTransaction( blockchain, mutableWorldState.updater(), @@ -107,7 +107,7 @@ public Optional afterTransactionInBlock( blockHash, transactionHash, (transaction, blockHeader, blockchain, worldState, transactionProcessor) -> { - final ProtocolSpec spec = protocolSchedule.getByBlockNumber(blockHeader.getNumber()); + final ProtocolSpec spec = protocolSchedule.getByBlockHeader(blockHeader); transactionProcessor.processTransaction( blockchain, worldState.updater(), @@ -139,7 +139,7 @@ private Optional performActionWithBlock( if (body == null) { return Optional.empty(); } - final ProtocolSpec protocolSpec = protocolSchedule.getByBlockNumber(header.getNumber()); + final ProtocolSpec protocolSpec = protocolSchedule.getByBlockHeader(header); final MainnetTransactionProcessor transactionProcessor = protocolSpec.getTransactionProcessor(); final BlockHeader previous = blockchain.getBlockHeader(header.getParentHash()).orElse(null); if (previous == null) { @@ -172,7 +172,7 @@ private Optional getBlock(final Hash blockHash) { private Optional getBadBlock(final Hash blockHash) { final ProtocolSpec protocolSpec = - protocolSchedule.getByBlockNumber(blockchain.getChainHeadHeader().getNumber()); + protocolSchedule.getByBlockHeader(blockchain.getChainHeadHeader()); return protocolSpec.getBadBlocksManager().getBadBlock(blockHash); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockResult.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockResult.java index 9fe5fbdfcd9..a4c3e98b650 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockResult.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockResult.java @@ -14,10 +14,16 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results; +import static java.util.stream.Collectors.toList; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalParameter; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.core.Withdrawal; import java.util.List; +import java.util.Optional; import com.fasterxml.jackson.annotation.JsonGetter; import com.fasterxml.jackson.annotation.JsonInclude; @@ -47,7 +53,9 @@ "gasUsed", "timestamp", "uncles", - "transactions" + "transactions", + "withdrawalsRoot", + "withdrawals" }) public class BlockResult implements JsonRpcResult { @@ -73,6 +81,8 @@ public class BlockResult implements JsonRpcResult { protected final List transactions; private final List ommers; private final String coinbase; + private final String withdrawalsRoot; + private final List withdrawals; public BlockResult( final BlockHeader header, @@ -80,7 +90,7 @@ public BlockResult( final List ommers, final Difficulty totalDifficulty, final int size) { - this(header, transactions, ommers, totalDifficulty, size, false); + this(header, transactions, ommers, totalDifficulty, size, false, Optional.empty()); } public BlockResult( @@ -89,7 +99,8 @@ public BlockResult( final List ommers, final Difficulty totalDifficulty, final int size, - final boolean includeCoinbase) { + final boolean includeCoinbase, + final Optional> withdrawals) { this.number = Quantity.create(header.getNumber()); this.hash = header.getHash().toString(); this.mixHash = header.getMixHash().toString(); @@ -112,6 +123,14 @@ public BlockResult( this.ommers = ommers; this.transactions = transactions; this.coinbase = includeCoinbase ? header.getCoinbase().toString() : null; + this.withdrawalsRoot = + !header.getWithdrawalRoot().equals(Hash.EMPTY) + ? header.getWithdrawalRoot().toString() + : null; + this.withdrawals = + withdrawals + .map(w -> w.stream().map(WithdrawalParameter::fromWithdrawal).collect(toList())) + .orElse(null); } @JsonGetter(value = "number") @@ -224,4 +243,14 @@ public List getTransactions() { public String getCoinbase() { return coinbase; } + + @JsonGetter(value = "withdrawalsRoot") + public String getWithdrawalsRoot() { + return withdrawalsRoot; + } + + @JsonGetter(value = "withdrawals") + public List getWithdrawals() { + return withdrawals; + } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockResultFactory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockResultFactory.java index e4311753542..fa32449c830 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockResultFactory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockResultFactory.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalParameter; import org.hyperledger.besu.ethereum.api.query.BlockWithMetadata; import org.hyperledger.besu.ethereum.api.query.TransactionWithMetadata; import org.hyperledger.besu.ethereum.core.Block; @@ -23,6 +24,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import com.fasterxml.jackson.databind.JsonNode; @@ -54,7 +56,8 @@ public BlockResult transactionComplete( ommers, blockWithMetadata.getTotalDifficulty(), blockWithMetadata.getSize(), - includeCoinbase); + includeCoinbase, + blockWithMetadata.getWithdrawals()); } public BlockResult transactionComplete(final Block block) { @@ -82,7 +85,13 @@ public BlockResult transactionComplete(final Block block) { .map(TextNode::new) .collect(Collectors.toList()); return new BlockResult( - block.getHeader(), txs, ommers, block.getHeader().getDifficulty(), block.calculateSize()); + block.getHeader(), + txs, + ommers, + block.getHeader().getDifficulty(), + block.calculateSize(), + false, + block.getBody().getWithdrawals()); } public EngineGetPayloadResult enginePayloadTransactionComplete(final Block block) { @@ -95,6 +104,25 @@ public EngineGetPayloadResult enginePayloadTransactionComplete(final Block block return new EngineGetPayloadResult(block.getHeader(), txs); } + public EngineGetPayloadResultV2 enginePayloadTransactionCompleteV2(final Block block) { + final List txs = + block.getBody().getTransactions().stream() + .map(TransactionEncoder::encodeOpaqueBytes) + .map(Bytes::toHexString) + .collect(Collectors.toList()); + final Optional> withdrawals = + block + .getBody() + .getWithdrawals() + .map( + w -> + w.stream() + .map(WithdrawalParameter::fromWithdrawal) + .collect(Collectors.toList())); + return new EngineGetPayloadResultV2( + new PayloadResultV2(block.getHeader(), txs, withdrawals), Quantity.create(0)); + } + public BlockResult transactionHash(final BlockWithMetadata blockWithMetadata) { return transactionHash(blockWithMetadata, false); } @@ -117,6 +145,7 @@ public BlockResult transactionHash( ommers, blockWithMetadata.getTotalDifficulty(), blockWithMetadata.getSize(), - includeCoinbase); + includeCoinbase, + blockWithMetadata.getWithdrawals()); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/ConsensusBlockResult.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/ConsensusBlockResult.java index a34eb832cce..600006c2f94 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/ConsensusBlockResult.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/ConsensusBlockResult.java @@ -18,6 +18,7 @@ import org.hyperledger.besu.ethereum.core.Difficulty; import java.util.List; +import java.util.Optional; import com.fasterxml.jackson.annotation.JsonGetter; import com.fasterxml.jackson.annotation.JsonPropertyOrder; @@ -57,7 +58,7 @@ public ConsensusBlockResult( final Difficulty totalDifficulty, final int size, final boolean includeCoinbase) { - super(header, null, ommers, totalDifficulty, size, includeCoinbase); + super(header, null, ommers, totalDifficulty, size, includeCoinbase, Optional.empty()); this.opaqueTransactions = transactions; } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV2.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV2.java new file mode 100644 index 00000000000..a93ffa70061 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV2.java @@ -0,0 +1,42 @@ +/* + * Copyright Hyperledger 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.api.jsonrpc.internal.results; + +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonPropertyOrder({ + "executionPayload", + "blockValue", +}) +public class EngineGetPayloadResultV2 { + protected final PayloadResultV2 executionPayload; + private final String blockValue; + + public EngineGetPayloadResultV2(final PayloadResultV2 executionPayload, final String blockValue) { + this.executionPayload = executionPayload; + this.blockValue = blockValue; + } + + @JsonGetter(value = "executionPayload") + public PayloadResultV2 getExecutionPayload() { + return executionPayload; + } + + @JsonGetter(value = "blockValue") + public String getBlockValue() { + return blockValue; + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/PayloadResultV2.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/PayloadResultV2.java new file mode 100644 index 00000000000..9db4c8ad302 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/PayloadResultV2.java @@ -0,0 +1,158 @@ +/* + * Copyright Hyperledger 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.api.jsonrpc.internal.results; + +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalParameter; +import org.hyperledger.besu.ethereum.core.BlockHeader; + +import java.util.List; +import java.util.Optional; + +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import org.apache.tuweni.bytes.Bytes32; + +@JsonPropertyOrder({ + "parentHash", + "feeRecipient", + "stateRoot", + "receiptsRoot", + "logsBloom", + "prevRandao", + "blockNumber", + "gasLimit", + "gasUsed", + "timestamp", + "extraData", + "baseFeePerGas", + "blockHash", + "transactions", + "withdrawals" +}) +public class PayloadResultV2 { + protected final String blockHash; + private final String parentHash; + private final String feeRecipient; + private final String stateRoot; + private final String receiptsRoot; + private final String logsBloom; + private final String prevRandao; + private final String blockNumber; + private final String gasLimit; + private final String gasUsed; + private final String timestamp; + private final String extraData; + private final String baseFeePerGas; + protected final List transactions; + protected final Optional> withdrawals; + + public PayloadResultV2( + final BlockHeader header, + final List transactions, + final Optional> withdrawals) { + this.blockNumber = Quantity.create(header.getNumber()); + this.blockHash = header.getHash().toString(); + this.parentHash = header.getParentHash().toString(); + this.logsBloom = header.getLogsBloom().toString(); + this.stateRoot = header.getStateRoot().toString(); + this.receiptsRoot = header.getReceiptsRoot().toString(); + this.extraData = header.getExtraData().toString(); + this.baseFeePerGas = header.getBaseFee().map(Quantity::create).orElse(null); + this.gasLimit = Quantity.create(header.getGasLimit()); + this.gasUsed = Quantity.create(header.getGasUsed()); + this.timestamp = Quantity.create(header.getTimestamp()); + this.transactions = transactions; + this.feeRecipient = header.getCoinbase().toString(); + this.prevRandao = header.getPrevRandao().map(Bytes32::toHexString).orElse(null); + this.withdrawals = withdrawals; + } + + @JsonGetter(value = "blockNumber") + public String getNumber() { + return blockNumber; + } + + @JsonGetter(value = "blockHash") + public String getHash() { + return blockHash; + } + + @JsonGetter(value = "parentHash") + public String getParentHash() { + return parentHash; + } + + @JsonGetter(value = "logsBloom") + public String getLogsBloom() { + return logsBloom; + } + + @JsonGetter(value = "prevRandao") + public String getPrevRandao() { + return prevRandao; + } + + @JsonGetter(value = "stateRoot") + public String getStateRoot() { + return stateRoot; + } + + @JsonGetter(value = "receiptsRoot") + public String getReceiptRoot() { + return receiptsRoot; + } + + @JsonGetter(value = "extraData") + public String getExtraData() { + return extraData; + } + + @JsonGetter(value = "baseFeePerGas") + public String getBaseFeePerGas() { + return baseFeePerGas; + } + + @JsonGetter(value = "gasLimit") + public String getGasLimit() { + return gasLimit; + } + + @JsonGetter(value = "gasUsed") + public String getGasUsed() { + return gasUsed; + } + + @JsonGetter(value = "timestamp") + public String getTimestamp() { + return timestamp; + } + + @JsonGetter(value = "transactions") + public List getTransactions() { + return transactions; + } + + @JsonGetter(value = "feeRecipient") + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getFeeRecipient() { + return feeRecipient; + } + + @JsonGetter(value = "withdrawals") + public List getWithdrawals() { + return withdrawals.orElse(null); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/UncleBlockResult.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/UncleBlockResult.java index 68a1c5ff6f4..7e593d9165c 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/UncleBlockResult.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/UncleBlockResult.java @@ -20,6 +20,7 @@ import org.hyperledger.besu.ethereum.core.Difficulty; import java.util.Collections; +import java.util.Optional; public class UncleBlockResult { @@ -30,7 +31,8 @@ public class UncleBlockResult { * @return A BlockResult, generated from the header and empty body. */ public static BlockResult build(final BlockHeader header) { - final BlockBody body = new BlockBody(Collections.emptyList(), Collections.emptyList()); + final BlockBody body = + new BlockBody(Collections.emptyList(), Collections.emptyList(), Optional.empty()); final int size = new Block(header, body).calculateSize(); return new BlockResult( header, Collections.emptyList(), Collections.emptyList(), Difficulty.ZERO, size); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ExecutionEngineJsonRpcMethods.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ExecutionEngineJsonRpcMethods.java index a29022aa0ab..b0a69bc4f1c 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ExecutionEngineJsonRpcMethods.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ExecutionEngineJsonRpcMethods.java @@ -20,12 +20,16 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineExchangeTransitionConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineForkchoiceUpdated; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineForkchoiceUpdatedV2; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetPayload; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetPayloadV2; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineNewPayload; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineNewPayloadV2; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineQosTimer; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory; import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator; import org.hyperledger.besu.ethereum.eth.manager.EthPeers; +import org.hyperledger.besu.ethereum.mainnet.TimestampSchedule; import java.util.Map; import java.util.Optional; @@ -37,12 +41,14 @@ public class ExecutionEngineJsonRpcMethods extends ApiGroupJsonRpcMethods { private final BlockResultFactory blockResultFactory = new BlockResultFactory(); private final Optional mergeCoordinator; + private final TimestampSchedule protocolSchedule; private final ProtocolContext protocolContext; private final EthPeers ethPeers; private final Vertx consensusEngineServer; ExecutionEngineJsonRpcMethods( final MiningCoordinator miningCoordinator, + final TimestampSchedule protocolSchedule, final ProtocolContext protocolContext, final EthPeers ethPeers, final Vertx consensusEngineServer) { @@ -50,7 +56,7 @@ public class ExecutionEngineJsonRpcMethods extends ApiGroupJsonRpcMethods { Optional.ofNullable(miningCoordinator) .filter(mc -> mc.isCompatibleWithEngineApi()) .map(MergeMiningCoordinator.class::cast); - + this.protocolSchedule = protocolSchedule; this.protocolContext = protocolContext; this.ethPeers = ethPeers; this.consensusEngineServer = consensusEngineServer; @@ -73,14 +79,32 @@ protected Map create() { mergeCoordinator.get(), blockResultFactory, engineQosTimer), + new EngineGetPayloadV2( + consensusEngineServer, + protocolContext, + mergeCoordinator.get(), + blockResultFactory, + engineQosTimer), new EngineNewPayload( consensusEngineServer, protocolContext, mergeCoordinator.get(), ethPeers, engineQosTimer), + new EngineNewPayloadV2( + consensusEngineServer, + protocolContext, + mergeCoordinator.get(), + ethPeers, + engineQosTimer), new EngineForkchoiceUpdated( consensusEngineServer, protocolContext, mergeCoordinator.get(), engineQosTimer), + new EngineForkchoiceUpdatedV2( + consensusEngineServer, + protocolSchedule, + protocolContext, + mergeCoordinator.get(), + engineQosTimer), new EngineExchangeTransitionConfiguration( consensusEngineServer, protocolContext, engineQosTimer)); } else { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java index 3e8c25fac99..ffb371bc8bc 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.methods; import org.hyperledger.besu.config.GenesisConfigOptions; +import org.hyperledger.besu.consensus.merge.MergeProtocolSchedule; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.filter.FilterManager; @@ -104,7 +105,12 @@ public Map methods( new EeaJsonRpcMethods( blockchainQueries, protocolSchedule, transactionPool, privacyParameters), new ExecutionEngineJsonRpcMethods( - miningCoordinator, protocolContext, ethPeers, consensusEngineServer), + miningCoordinator, + MergeProtocolSchedule.createTimestamp( + genesisConfigOptions, privacyParameters, false), + protocolContext, + ethPeers, + consensusEngineServer), new GoQuorumJsonRpcPrivacyMethods( blockchainQueries, protocolSchedule, transactionPool, privacyParameters), new EthJsonRpcMethods( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/BlockWithMetadata.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/BlockWithMetadata.java index 6551e8b1611..def3aaea436 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/BlockWithMetadata.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/BlockWithMetadata.java @@ -16,8 +16,10 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.core.Withdrawal; import java.util.List; +import java.util.Optional; public class BlockWithMetadata { @@ -26,6 +28,7 @@ public class BlockWithMetadata { private final List ommers; private final Difficulty totalDifficulty; private final int size; + private final Optional> withdrawals; /** * @param header The block header @@ -40,11 +43,22 @@ public BlockWithMetadata( final List ommers, final Difficulty totalDifficulty, final int size) { + this(header, transactions, ommers, totalDifficulty, size, Optional.empty()); + } + + public BlockWithMetadata( + final BlockHeader header, + final List transactions, + final List ommers, + final Difficulty totalDifficulty, + final int size, + final Optional> withdrawals) { this.header = header; this.transactions = transactions; this.ommers = ommers; this.totalDifficulty = totalDifficulty; this.size = size; + this.withdrawals = withdrawals; } public BlockHeader getHeader() { @@ -66,4 +80,8 @@ public Difficulty getTotalDifficulty() { public int getSize() { return size; } + + public Optional> getWithdrawals() { + return withdrawals; + } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueries.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueries.java index 0c79a7ccd2a..be0cb1ab51e 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueries.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueries.java @@ -414,7 +414,12 @@ public Optional> blockByHash( .collect(Collectors.toList()); final int size = new Block(header, body).calculateSize(); return new BlockWithMetadata<>( - header, formattedTxs, ommers, td, size); + header, + formattedTxs, + ommers, + td, + size, + body.getWithdrawals()); }))); } @@ -468,7 +473,8 @@ public Optional> blockByHashWithTxHashes( .map(BlockHeader::getHash) .collect(Collectors.toList()); final int size = new Block(header, body).calculateSize(); - return new BlockWithMetadata<>(header, txs, ommers, td, size); + return new BlockWithMetadata<>( + header, txs, ommers, td, size, body.getWithdrawals()); }))); } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceRpcApisTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceRpcApisTest.java index c2b08dd9ece..9974365aa5f 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceRpcApisTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceRpcApisTest.java @@ -278,7 +278,8 @@ private P2PNetwork createP2pNetwork() { .metricsSystem(new NoOpMetricsSystem()) .storageProvider(new InMemoryKeyValueStorageProvider()) .blockchain(blockchain) - .forks(Collections.emptyList()) + .blockNumberForks(Collections.emptyList()) + .timestampForks(Collections.emptyList()) .build(); p2pNetwork.start(); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGasPriceTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGasPriceTest.java index 59737e8cad8..78f0dc1da27 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGasPriceTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGasPriceTest.java @@ -161,6 +161,7 @@ private Object createFakeBlock(final Long height) { Wei.ZERO, Hash.EMPTY, 0, + Hash.EMPTY, null), new BlockBody( List.of( @@ -174,7 +175,8 @@ private Object createFakeBlock(final Long height) { Bytes.EMPTY, Address.ZERO, Optional.empty())), - List.of()))); + List.of(), + Optional.empty()))); } private Object createEmptyBlock(final Long height) { @@ -197,8 +199,9 @@ private Object createEmptyBlock(final Long height) { Wei.ZERO, Hash.EMPTY, 0, + Hash.EMPTY, null), - new BlockBody(List.of(), List.of()))); + new BlockBody(List.of(), List.of(), Optional.empty()))); } private JsonRpcRequestContext requestWithParams(final Object... params) { diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionReceiptTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionReceiptTest.java index ff3e1d54802..4240e32d54e 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionReceiptTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionReceiptTest.java @@ -111,7 +111,9 @@ public class EthGetTransactionReceiptTest { GasLimitCalculator.constant(), FeeMarket.legacy(), null, - Optional.of(PoWHasher.ETHASH_LIGHT)); + Optional.of(PoWHasher.ETHASH_LIGHT), + null, + null); private final ProtocolSpec statusTransactionTypeSpec = new ProtocolSpec( "status", @@ -136,7 +138,9 @@ public class EthGetTransactionReceiptTest { GasLimitCalculator.constant(), FeeMarket.legacy(), null, - Optional.of(PoWHasher.ETHASH_LIGHT)); + Optional.of(PoWHasher.ETHASH_LIGHT), + null, + null); @SuppressWarnings("unchecked") private final ProtocolSchedule protocolSchedule = mock(ProtocolSchedule.class); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleByBlockHashAndIndexTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleByBlockHashAndIndexTest.java index 6f59f146d39..07a52077ac1 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleByBlockHashAndIndexTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleByBlockHashAndIndexTest.java @@ -146,7 +146,9 @@ public void shouldReturnExpectedBlockResult() { private BlockResult blockResult(final BlockHeader header) { final Block block = - new Block(header, new BlockBody(Collections.emptyList(), Collections.emptyList())); + new Block( + header, + new BlockBody(Collections.emptyList(), Collections.emptyList(), Optional.empty())); return new BlockResult( header, Collections.emptyList(), diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleByBlockNumberAndIndexTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleByBlockNumberAndIndexTest.java index f07d55178f6..c94113e10a0 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleByBlockNumberAndIndexTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleByBlockNumberAndIndexTest.java @@ -123,7 +123,9 @@ public void shouldReturnExpectedBlockResult() { private BlockResult blockResult(final BlockHeader header) { final Block block = - new Block(header, new BlockBody(Collections.emptyList(), Collections.emptyList())); + new Block( + header, + new BlockBody(Collections.emptyList(), Collections.emptyList(), Optional.empty())); return new BlockResult( header, Collections.emptyList(), diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeTransitionConfigurationTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeTransitionConfigurationTest.java index c9e481db55b..0e0aabc680c 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeTransitionConfigurationTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeTransitionConfigurationTest.java @@ -249,6 +249,7 @@ private BlockHeader createBlockHeader(final Hash blockHash, final long blockNumb Wei.ZERO, Bytes32.ZERO, 0, + Hash.EMPTY, new BlockHeaderFunctions() { @Override public Hash hash(final BlockHeader header) { diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedTest.java index 6ad01828fb4..34950455752 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedTest.java @@ -39,6 +39,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EngineForkchoiceUpdatedParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EnginePayloadAttributesParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponseType; @@ -174,8 +175,7 @@ public void shouldReturnInvalidOnOldTimestamp() { when(mergeContext.isSyncing()).thenReturn(false); when(mergeCoordinator.getOrSyncHeadByHash(mockHeader.getHash(), parent.getHash())) .thenReturn(Optional.of(mockHeader)); - when(mergeCoordinator.updateForkChoice( - mockHeader, parent.getHash(), parent.getHash(), Optional.empty())) + when(mergeCoordinator.updateForkChoice(mockHeader, parent.getHash(), parent.getHash())) .thenReturn( ForkchoiceResult.withFailure( ForkchoiceResult.Status.INVALID, @@ -234,7 +234,11 @@ public void shouldReturnValidWithoutFinalizedWithPayload() { payloadParams.getSuggestedFeeRecipient()); when(mergeCoordinator.preparePayload( - mockHeader, payloadParams.getTimestamp(), payloadParams.getPrevRandao(), Address.ECREC)) + mockHeader, + payloadParams.getTimestamp(), + payloadParams.getPrevRandao(), + Address.ECREC, + Optional.empty())) .thenReturn(mockPayloadId); var res = @@ -402,8 +406,7 @@ public void shouldIgnoreUpdateToOldHeadAndNotPreparePayload() { when(mergeCoordinator.latestValidAncestorDescendsFromTerminal(mockHeader)).thenReturn(true); var ignoreOldHeadUpdateRes = ForkchoiceResult.withIgnoreUpdateToOldHead(mockHeader); - when(mergeCoordinator.updateForkChoice(any(), any(), any(), any())) - .thenReturn(ignoreOldHeadUpdateRes); + when(mergeCoordinator.updateForkChoice(any(), any(), any())).thenReturn(ignoreOldHeadUpdateRes); var payloadParams = new EnginePayloadAttributesParameter( @@ -420,7 +423,7 @@ public void shouldIgnoreUpdateToOldHeadAndNotPreparePayload() { var forkchoiceRes = (EngineUpdateForkchoiceResult) resp.getResult(); - verify(mergeCoordinator, never()).preparePayload(any(), any(), any(), any()); + verify(mergeCoordinator, never()).preparePayload(any(), any(), any(), any(), any()); assertThat(forkchoiceRes.getPayloadStatus().getStatus()).isEqualTo(EngineStatus.VALID); assertThat(forkchoiceRes.getPayloadStatus().getError()).isNull(); @@ -449,7 +452,7 @@ private EngineUpdateForkchoiceResult assertSuccessWithPayloadForForkchoiceResult // result from mergeCoordinator has no new finalized, new head: when(mergeCoordinator.updateForkChoice( - any(BlockHeader.class), any(Hash.class), any(Hash.class), any())) + any(BlockHeader.class), any(Hash.class), any(Hash.class))) .thenReturn(forkchoiceResult); var resp = resp(fcuParam, payloadParam); var res = fromSuccessResp(resp); @@ -511,4 +514,46 @@ private void assertInvalidForkchoiceState(final JsonRpcResponse resp) { assertThat(errorResp.getError().getCode()).isEqualTo(-38002); assertThat(errorResp.getError().getMessage()).isEqualTo("Invalid forkchoice state"); } + + private void assertInvalidForkchoiceState( + final JsonRpcResponse resp, final JsonRpcError jsonRpcError) { + assertThat(resp.getType()).isEqualTo(JsonRpcResponseType.ERROR); + + var errorResp = (JsonRpcErrorResponse) resp; + assertThat(errorResp.getError()).isEqualTo(jsonRpcError); + assertThat(errorResp.getError().getMessage()).isEqualTo(jsonRpcError.getMessage()); + } + + @Test + public void shouldReturnInvalidIfPayloadTimestampNotGreaterThanHead() { + var builder = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE); + BlockHeader mockParent = builder.number(9L).buildHeader(); + BlockHeader mockHeader = builder.number(10L).parentHash(mockParent.getHash()).buildHeader(); + when(blockchain.getBlockHeader(any())).thenReturn(Optional.of(mockHeader)); + when(mergeCoordinator.getOrSyncHeadByHash(mockHeader.getHash(), Hash.ZERO)) + .thenReturn(Optional.of(mockHeader)); + + when(mergeCoordinator.isDescendantOf(any(), any())).thenReturn(true); + when(mergeCoordinator.updateForkChoice( + any(BlockHeader.class), any(Hash.class), any(Hash.class))) + .thenReturn(ForkchoiceResult.withResult(Optional.of(mockParent), Optional.of(mockHeader))); + when(mergeCoordinator.latestValidAncestorDescendsFromTerminal(mockHeader)).thenReturn(true); + + final long timestampNotGreaterThanHead = mockHeader.getTimestamp(); + + var payloadParams = + new EnginePayloadAttributesParameter( + String.valueOf(timestampNotGreaterThanHead), + Bytes32.fromHexStringLenient("0xDEADBEEF").toHexString(), + Address.ECREC.toString()); + + var resp = + resp( + new EngineForkchoiceUpdatedParameter( + mockHeader.getHash(), Hash.ZERO, mockParent.getHash()), + Optional.of(payloadParams)); + + assertInvalidForkchoiceState(resp, JsonRpcError.INVALID_PAYLOAD_ATTRIBUTES); + verify(engineCallListener, times(1)).executionEngineCalled(); + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV2Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV2Test.java new file mode 100644 index 00000000000..2a5746cb4e8 --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV2Test.java @@ -0,0 +1,707 @@ +/* + * Copyright Hyperledger 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.api.jsonrpc.internal.methods.engine; + +import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.INVALID; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.SYNCING; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.VALID; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.consensus.merge.MergeContext; +import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator; +import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator.ForkchoiceResult; +import org.hyperledger.besu.consensus.merge.blockcreation.PayloadIdentifier; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EngineForkchoiceUpdatedParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EnginePayloadAttributesParameterV2; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponseType; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineUpdateForkchoiceResult; +import org.hyperledger.besu.ethereum.chain.MutableBlockchain; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.mainnet.TimestampSchedule; +import org.hyperledger.besu.ethereum.mainnet.WithdrawalsValidator; + +import java.util.Optional; +import java.util.stream.Stream; + +import io.vertx.core.Vertx; +import org.apache.tuweni.bytes.Bytes32; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +// TODO Withdrawals make a thin V1/V2 layer OR dry up these tests (copied from +// EngineForkchoiceUpdatedTest) +@RunWith(MockitoJUnitRunner.class) +public class EngineForkchoiceUpdatedV2Test { + + private EngineForkchoiceUpdatedV2 method; + private static final Vertx vertx = Vertx.vertx(); + private static final Hash mockHash = Hash.hash(Bytes32.fromHexStringLenient("0x1337deadbeef")); + + private static final EngineForkchoiceUpdatedParameter mockFcuParam = + new EngineForkchoiceUpdatedParameter(mockHash, mockHash, mockHash); + + @Mock private ProtocolSpec protocolSpec; + @Mock private TimestampSchedule protocolSchedule; + @Mock private ProtocolContext protocolContext; + + @Mock private MergeContext mergeContext; + + @Mock private MergeMiningCoordinator mergeCoordinator; + + @Mock private MutableBlockchain blockchain; + + @Mock private EngineCallListener engineCallListener; + + @Before + public void before() { + when(protocolContext.safeConsensusContext(Mockito.any())).thenReturn(Optional.of(mergeContext)); + when(protocolContext.getBlockchain()).thenReturn(blockchain); + when(protocolSpec.getWithdrawalsValidator()) + .thenReturn(new WithdrawalsValidator.ProhibitedWithdrawals()); + when(protocolSchedule.getByTimestamp(anyLong())).thenReturn(Optional.of(protocolSpec)); + this.method = + new EngineForkchoiceUpdatedV2( + vertx, protocolSchedule, protocolContext, mergeCoordinator, engineCallListener); + } + + @Test + public void shouldReturnExpectedMethodName() { + // will break as specs change, intentional: + assertThat(method.getName()).isEqualTo("engine_forkchoiceUpdatedV2"); + } + + @Test + public void shouldReturnSyncingIfForwardSync() { + when(mergeContext.isSyncing()).thenReturn(true); + assertSuccessWithPayloadForForkchoiceResult( + mockFcuParam, Optional.empty(), mock(ForkchoiceResult.class), SYNCING); + } + + @Test + public void shouldReturnSyncingIfMissingNewHead() { + assertSuccessWithPayloadForForkchoiceResult( + mockFcuParam, Optional.empty(), mock(ForkchoiceResult.class), SYNCING); + } + + @Test + public void shouldReturnInvalidWithLatestValidHashOnBadBlock() { + BlockHeader mockHeader = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE).buildHeader(); + Hash latestValidHash = Hash.hash(Bytes32.fromHexStringLenient("0xcafebabe")); + when(mergeCoordinator.isBadBlock(mockHeader.getHash())).thenReturn(true); + when(mergeCoordinator.getLatestValidHashOfBadBlock(mockHeader.getHash())) + .thenReturn(Optional.of(latestValidHash)); + + assertSuccessWithPayloadForForkchoiceResult( + new EngineForkchoiceUpdatedParameter( + mockHeader.getHash(), Hash.ZERO, mockHeader.getParentHash()), + Optional.empty(), + mock(ForkchoiceResult.class), + INVALID, + Optional.of(latestValidHash)); + } + + @Test + public void shouldReturnSyncingOnHeadNotFound() { + assertSuccessWithPayloadForForkchoiceResult( + mockFcuParam, Optional.empty(), mock(ForkchoiceResult.class), SYNCING); + } + + @Test + public void shouldReturnValidWithoutFinalizedOrPayload() { + BlockHeader mockHeader = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE).buildHeader(); + when(mergeCoordinator.getOrSyncHeadByHash(mockHeader.getHash(), Hash.ZERO)) + .thenReturn(Optional.of(mockHeader)); + + assertSuccessWithPayloadForForkchoiceResult( + new EngineForkchoiceUpdatedParameter(mockHeader.getHash(), Hash.ZERO, Hash.ZERO), + Optional.empty(), + ForkchoiceResult.withResult(Optional.empty(), Optional.of(mockHeader)), + VALID); + } + + @Test + public void shouldReturnInvalidOnOldTimestamp() { + BlockHeader parent = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE).buildHeader(); + BlockHeader mockHeader = + new BlockHeaderTestFixture() + .baseFeePerGas(Wei.ONE) + .parentHash(parent.getHash()) + .timestamp(parent.getTimestamp()) + .buildHeader(); + when(blockchain.getBlockHeader(parent.getHash())).thenReturn(Optional.of(parent)); + + when(mergeCoordinator.isDescendantOf(any(), any())).thenReturn(true); + when(mergeContext.isSyncing()).thenReturn(false); + when(mergeCoordinator.getOrSyncHeadByHash(mockHeader.getHash(), parent.getHash())) + .thenReturn(Optional.of(mockHeader)); + when(mergeCoordinator.updateForkChoice(mockHeader, parent.getHash(), parent.getHash())) + .thenReturn( + ForkchoiceResult.withFailure( + ForkchoiceResult.Status.INVALID, + "new head timestamp not greater than parent", + Optional.of(parent.getHash()))); + + EngineForkchoiceUpdatedParameter param = + new EngineForkchoiceUpdatedParameter( + mockHeader.getBlockHash(), parent.getBlockHash(), parent.getBlockHash()); + + EngineUpdateForkchoiceResult resp = fromSuccessResp(resp(param, Optional.empty())); + + assertThat(resp.getPayloadStatus().getStatus()).isEqualTo(INVALID); + assertThat(resp.getPayloadStatus().getLatestValidHash()).isPresent(); + assertThat(resp.getPayloadStatus().getLatestValidHash().get()).isEqualTo(parent.getBlockHash()); + assertThat(resp.getPayloadStatus().getError()) + .isEqualTo("new head timestamp not greater than parent"); + verify(engineCallListener, times(1)).executionEngineCalled(); + } + + @Test + public void shouldReturnValidWithNewHeadAndFinalizedNoPayload() { + var builder = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE); + BlockHeader mockParent = builder.number(9L).buildHeader(); + BlockHeader mockHeader = builder.number(10L).parentHash(mockParent.getHash()).buildHeader(); + when(blockchain.getBlockHeader(any())).thenReturn(Optional.of(mockHeader)); + when(mergeCoordinator.getOrSyncHeadByHash(mockHeader.getHash(), Hash.ZERO)) + .thenReturn(Optional.of(mockHeader)); + + when(mergeCoordinator.isDescendantOf(any(), any())).thenReturn(true); + + assertSuccessWithPayloadForForkchoiceResult( + new EngineForkchoiceUpdatedParameter(mockHeader.getHash(), Hash.ZERO, mockParent.getHash()), + Optional.empty(), + ForkchoiceResult.withResult(Optional.of(mockParent), Optional.of(mockHeader)), + VALID); + } + + @Test + public void shouldReturnValidWithoutFinalizedWithPayload() { + BlockHeader mockHeader = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE).buildHeader(); + when(mergeCoordinator.getOrSyncHeadByHash(mockHeader.getHash(), Hash.ZERO)) + .thenReturn(Optional.of(mockHeader)); + + var payloadParams = + new EnginePayloadAttributesParameterV2( + String.valueOf(System.currentTimeMillis()), + Bytes32.fromHexStringLenient("0xDEADBEEF").toHexString(), + Address.ECREC.toString(), + null); + var mockPayloadId = + PayloadIdentifier.forPayloadParams( + mockHeader.getHash(), + payloadParams.getTimestamp(), + payloadParams.getPrevRandao(), + payloadParams.getSuggestedFeeRecipient()); + + when(mergeCoordinator.preparePayload( + mockHeader, + payloadParams.getTimestamp(), + payloadParams.getPrevRandao(), + Address.ECREC, + Optional.empty())) + .thenReturn(mockPayloadId); + + var res = + assertSuccessWithPayloadForForkchoiceResult( + new EngineForkchoiceUpdatedParameter(mockHeader.getHash(), Hash.ZERO, Hash.ZERO), + Optional.of(payloadParams), + ForkchoiceResult.withResult(Optional.empty(), Optional.of(mockHeader)), + VALID); + + assertThat(res.getPayloadId()).isEqualTo(mockPayloadId.toHexString()); + } + + @Test + public void shouldReturnInvalidForkchoiceStateIfFinalizedBlockIsUnknown() { + BlockHeader newHead = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE).buildHeader(); + Hash finalizedBlockHash = Hash.hash(Bytes32.fromHexStringLenient("0x424abcdef")); + + when(blockchain.getBlockHeader(finalizedBlockHash)).thenReturn(Optional.empty()); + when(mergeContext.isSyncing()).thenReturn(false); + when(mergeCoordinator.getOrSyncHeadByHash(newHead.getHash(), finalizedBlockHash)) + .thenReturn(Optional.of(newHead)); + + var resp = + resp( + new EngineForkchoiceUpdatedParameter( + newHead.getBlockHash(), finalizedBlockHash, finalizedBlockHash), + Optional.empty()); + + assertInvalidForkchoiceState(resp); + verify(engineCallListener, times(1)).executionEngineCalled(); + } + + @Test + public void shouldReturnInvalidForkchoiceStateIfFinalizedBlockIsNotAnAncestorOfNewHead() { + BlockHeader finalized = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE).buildHeader(); + BlockHeader newHead = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE).buildHeader(); + + when(blockchain.getBlockHeader(newHead.getHash())).thenReturn(Optional.of(newHead)); + when(blockchain.getBlockHeader(finalized.getHash())).thenReturn(Optional.of(finalized)); + when(mergeContext.isSyncing()).thenReturn(false); + when(mergeCoordinator.getOrSyncHeadByHash(newHead.getHash(), finalized.getBlockHash())) + .thenReturn(Optional.of(newHead)); + when(mergeCoordinator.isDescendantOf(finalized, newHead)).thenReturn(false); + + var resp = + resp( + new EngineForkchoiceUpdatedParameter( + newHead.getBlockHash(), finalized.getBlockHash(), finalized.getBlockHash()), + Optional.empty()); + + assertInvalidForkchoiceState(resp); + verify(engineCallListener, times(1)).executionEngineCalled(); + } + + @Test + public void shouldReturnInvalidForkchoiceStateIfSafeHeadZeroWithFinalizedBlock() { + BlockHeader parent = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE).buildHeader(); + BlockHeader newHead = + new BlockHeaderTestFixture() + .baseFeePerGas(Wei.ONE) + .parentHash(parent.getHash()) + .timestamp(parent.getTimestamp()) + .buildHeader(); + + when(blockchain.getBlockHeader(parent.getHash())).thenReturn(Optional.of(parent)); + when(mergeContext.isSyncing()).thenReturn(false); + when(mergeCoordinator.getOrSyncHeadByHash(newHead.getHash(), parent.getBlockHash())) + .thenReturn(Optional.of(newHead)); + + var resp = + resp( + new EngineForkchoiceUpdatedParameter( + newHead.getBlockHash(), parent.getBlockHash(), Hash.ZERO), + Optional.empty()); + + assertInvalidForkchoiceState(resp); + verify(engineCallListener, times(1)).executionEngineCalled(); + } + + @Test + public void shouldReturnInvalidForkchoiceStateIfSafeBlockIsUnknown() { + BlockHeader finalized = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE).buildHeader(); + BlockHeader newHead = + new BlockHeaderTestFixture() + .baseFeePerGas(Wei.ONE) + .parentHash(finalized.getHash()) + .timestamp(finalized.getTimestamp()) + .buildHeader(); + Hash safeBlockBlockHash = Hash.hash(Bytes32.fromHexStringLenient("0x424abcdef")); + + when(blockchain.getBlockHeader(finalized.getHash())).thenReturn(Optional.of(finalized)); + when(blockchain.getBlockHeader(safeBlockBlockHash)).thenReturn(Optional.empty()); + when(mergeContext.isSyncing()).thenReturn(false); + when(mergeCoordinator.getOrSyncHeadByHash(newHead.getHash(), finalized.getBlockHash())) + .thenReturn(Optional.of(newHead)); + when(mergeCoordinator.isDescendantOf(finalized, newHead)).thenReturn(true); + + var resp = + resp( + new EngineForkchoiceUpdatedParameter( + newHead.getBlockHash(), finalized.getBlockHash(), safeBlockBlockHash), + Optional.empty()); + + assertInvalidForkchoiceState(resp); + verify(engineCallListener, times(1)).executionEngineCalled(); + } + + @Test + public void shouldReturnInvalidForkchoiceStateIfSafeBlockIsNotADescendantOfFinalized() { + BlockHeader finalized = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE).buildHeader(); + BlockHeader newHead = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE).buildHeader(); + BlockHeader safeBlock = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE).buildHeader(); + + when(blockchain.getBlockHeader(newHead.getHash())).thenReturn(Optional.of(newHead)); + when(blockchain.getBlockHeader(finalized.getHash())).thenReturn(Optional.of(finalized)); + when(blockchain.getBlockHeader(safeBlock.getHash())).thenReturn(Optional.of(safeBlock)); + when(mergeContext.isSyncing()).thenReturn(false); + when(mergeCoordinator.getOrSyncHeadByHash(newHead.getHash(), finalized.getBlockHash())) + .thenReturn(Optional.of(newHead)); + when(mergeCoordinator.isDescendantOf(finalized, newHead)).thenReturn(true); + when(mergeCoordinator.isDescendantOf(finalized, safeBlock)).thenReturn(false); + + var resp = + resp( + new EngineForkchoiceUpdatedParameter( + newHead.getBlockHash(), finalized.getBlockHash(), safeBlock.getBlockHash()), + Optional.empty()); + + assertInvalidForkchoiceState(resp); + verify(engineCallListener, times(1)).executionEngineCalled(); + } + + @Test + public void shouldReturnInvalidForkchoiceStateIfSafeBlockIsNotAnAncestorOfNewHead() { + BlockHeader finalized = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE).buildHeader(); + BlockHeader newHead = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE).buildHeader(); + BlockHeader safeBlock = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE).buildHeader(); + + when(blockchain.getBlockHeader(newHead.getHash())).thenReturn(Optional.of(newHead)); + when(blockchain.getBlockHeader(finalized.getHash())).thenReturn(Optional.of(finalized)); + when(blockchain.getBlockHeader(safeBlock.getHash())).thenReturn(Optional.of(safeBlock)); + when(mergeContext.isSyncing()).thenReturn(false); + when(mergeCoordinator.getOrSyncHeadByHash(newHead.getHash(), finalized.getBlockHash())) + .thenReturn(Optional.of(newHead)); + when(mergeCoordinator.isDescendantOf(finalized, newHead)).thenReturn(true); + when(mergeCoordinator.isDescendantOf(finalized, safeBlock)).thenReturn(true); + when(mergeCoordinator.isDescendantOf(safeBlock, newHead)).thenReturn(false); + + var resp = + resp( + new EngineForkchoiceUpdatedParameter( + newHead.getBlockHash(), finalized.getBlockHash(), safeBlock.getBlockHash()), + Optional.empty()); + + assertInvalidForkchoiceState(resp); + verify(engineCallListener, times(1)).executionEngineCalled(); + } + + @Test + public void shouldIgnoreUpdateToOldHeadAndNotPreparePayload() { + BlockHeader mockHeader = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE).buildHeader(); + + when(mergeCoordinator.getOrSyncHeadByHash(mockHeader.getHash(), Hash.ZERO)) + .thenReturn(Optional.of(mockHeader)); + + var ignoreOldHeadUpdateRes = ForkchoiceResult.withIgnoreUpdateToOldHead(mockHeader); + when(mergeCoordinator.updateForkChoice(any(), any(), any())).thenReturn(ignoreOldHeadUpdateRes); + + var payloadParams = + new EnginePayloadAttributesParameterV2( + String.valueOf(System.currentTimeMillis()), + Bytes32.fromHexStringLenient("0xDEADBEEF").toHexString(), + Address.ECREC.toString(), + null); + + var resp = + (JsonRpcSuccessResponse) + resp( + new EngineForkchoiceUpdatedParameter( + mockHeader.getBlockHash(), Hash.ZERO, Hash.ZERO), + Optional.of(payloadParams)); + + var forkchoiceRes = (EngineUpdateForkchoiceResult) resp.getResult(); + + verify(mergeCoordinator, never()).preparePayload(any(), any(), any(), any(), any()); + + assertThat(forkchoiceRes.getPayloadStatus().getStatus()).isEqualTo(EngineStatus.VALID); + assertThat(forkchoiceRes.getPayloadStatus().getError()).isNull(); + assertThat(forkchoiceRes.getPayloadStatus().getLatestValidHashAsString()) + .isEqualTo(mockHeader.getHash().toHexString()); + assertThat(forkchoiceRes.getPayloadId()).isNull(); + assertThat(forkchoiceRes.getPayloadId()).isNull(); + verify(engineCallListener, times(1)).executionEngineCalled(); + } + + @Test + public void shouldReturnInvalidIfWithdrawalsIsNotNull_WhenWithdrawalsProhibited() { + var builder = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE); + BlockHeader mockParent = builder.number(9L).buildHeader(); + BlockHeader mockHeader = builder.number(10L).parentHash(mockParent.getHash()).buildHeader(); + when(blockchain.getBlockHeader(any())).thenReturn(Optional.of(mockHeader)); + when(mergeCoordinator.getOrSyncHeadByHash(mockHeader.getHash(), Hash.ZERO)) + .thenReturn(Optional.of(mockHeader)); + + when(mergeCoordinator.isDescendantOf(any(), any())).thenReturn(true); + when(mergeCoordinator.updateForkChoice( + any(BlockHeader.class), any(Hash.class), any(Hash.class))) + .thenReturn(ForkchoiceResult.withResult(Optional.of(mockParent), Optional.of(mockHeader))); + + var payloadParams = + new EnginePayloadAttributesParameterV2( + String.valueOf(System.currentTimeMillis()), + Bytes32.fromHexStringLenient("0xDEADBEEF").toHexString(), + Address.ECREC.toString(), + emptyList()); + + var resp = + resp( + new EngineForkchoiceUpdatedParameter( + mockHeader.getHash(), Hash.ZERO, mockParent.getHash()), + Optional.of(payloadParams)); + + assertInvalidForkchoiceState(resp, JsonRpcError.INVALID_PARAMS); + verify(engineCallListener, times(1)).executionEngineCalled(); + } + + @Test + public void shouldReturnValidIfWithdrawalsIsNull_WhenWithdrawalsProhibited() { + // https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#specification-3 + // If the [withdrawal] functionality is not activated, client software MUST return + // error -38003: Invalid payload attributes if payloadAttributes.withdrawals is not null + var builder = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE); + BlockHeader mockParent = builder.number(9L).buildHeader(); + BlockHeader mockHeader = builder.number(10L).parentHash(mockParent.getHash()).buildHeader(); + when(blockchain.getBlockHeader(any())).thenReturn(Optional.of(mockHeader)); + when(mergeCoordinator.getOrSyncHeadByHash(mockHeader.getHash(), Hash.ZERO)) + .thenReturn(Optional.of(mockHeader)); + + when(mergeCoordinator.isDescendantOf(any(), any())).thenReturn(true); + + var payloadParams = + new EnginePayloadAttributesParameterV2( + String.valueOf(System.currentTimeMillis()), + Bytes32.fromHexStringLenient("0xDEADBEEF").toHexString(), + Address.ECREC.toString(), + null); + + var mockPayloadId = + PayloadIdentifier.forPayloadParams( + mockHeader.getHash(), + payloadParams.getTimestamp(), + payloadParams.getPrevRandao(), + payloadParams.getSuggestedFeeRecipient()); + + when(mergeCoordinator.preparePayload( + mockHeader, + payloadParams.getTimestamp(), + payloadParams.getPrevRandao(), + Address.ECREC, + Optional.empty())) + .thenReturn(mockPayloadId); + + assertSuccessWithPayloadForForkchoiceResult( + new EngineForkchoiceUpdatedParameter(mockHeader.getHash(), Hash.ZERO, mockParent.getHash()), + Optional.of(payloadParams), + ForkchoiceResult.withResult(Optional.empty(), Optional.of(mockHeader)), + VALID); + verify(engineCallListener, times(1)).executionEngineCalled(); + } + + @Test + public void shouldReturnInvalidIfWithdrawalsIsNull_WhenWithdrawalsAllowed() { + var builder = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE); + BlockHeader mockParent = builder.number(9L).buildHeader(); + BlockHeader mockHeader = builder.number(10L).parentHash(mockParent.getHash()).buildHeader(); + when(blockchain.getBlockHeader(any())).thenReturn(Optional.of(mockHeader)); + when(mergeCoordinator.getOrSyncHeadByHash(mockHeader.getHash(), Hash.ZERO)) + .thenReturn(Optional.of(mockHeader)); + + when(mergeCoordinator.isDescendantOf(any(), any())).thenReturn(true); + when(mergeCoordinator.updateForkChoice( + any(BlockHeader.class), any(Hash.class), any(Hash.class))) + .thenReturn(ForkchoiceResult.withResult(Optional.of(mockParent), Optional.of(mockHeader))); + when(protocolSpec.getWithdrawalsValidator()) + .thenReturn(new WithdrawalsValidator.AllowedWithdrawals()); + + var payloadParams = + new EnginePayloadAttributesParameterV2( + String.valueOf(System.currentTimeMillis()), + Bytes32.fromHexStringLenient("0xDEADBEEF").toHexString(), + Address.ECREC.toString(), + null); + + var resp = + resp( + new EngineForkchoiceUpdatedParameter( + mockHeader.getHash(), Hash.ZERO, mockParent.getHash()), + Optional.of(payloadParams)); + + assertInvalidForkchoiceState(resp, JsonRpcError.INVALID_PARAMS); + verify(engineCallListener, times(1)).executionEngineCalled(); + } + + @Test + public void shouldReturnValidIfWithdrawalsIsNotNull_WhenWithdrawalsAllowed() { + // https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#specification-3 + // If withdrawal functionality is activated, client software MUST return + // error -38003: Invalid payload attributes if payloadAttributes.withdrawals is null + when(protocolSpec.getWithdrawalsValidator()) + .thenReturn(new WithdrawalsValidator.AllowedWithdrawals()); + + var builder = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE); + BlockHeader mockParent = builder.number(9L).buildHeader(); + BlockHeader mockHeader = builder.number(10L).parentHash(mockParent.getHash()).buildHeader(); + when(blockchain.getBlockHeader(any())).thenReturn(Optional.of(mockHeader)); + when(mergeCoordinator.getOrSyncHeadByHash(mockHeader.getHash(), Hash.ZERO)) + .thenReturn(Optional.of(mockHeader)); + + when(mergeCoordinator.isDescendantOf(any(), any())).thenReturn(true); + + var payloadParams = + new EnginePayloadAttributesParameterV2( + String.valueOf(System.currentTimeMillis()), + Bytes32.fromHexStringLenient("0xDEADBEEF").toHexString(), + Address.ECREC.toString(), + emptyList()); + + var mockPayloadId = + PayloadIdentifier.forPayloadParams( + mockHeader.getHash(), + payloadParams.getTimestamp(), + payloadParams.getPrevRandao(), + payloadParams.getSuggestedFeeRecipient()); + + when(mergeCoordinator.preparePayload( + mockHeader, + payloadParams.getTimestamp(), + payloadParams.getPrevRandao(), + Address.ECREC, + Optional.of(emptyList()))) + .thenReturn(mockPayloadId); + + assertSuccessWithPayloadForForkchoiceResult( + new EngineForkchoiceUpdatedParameter(mockHeader.getHash(), Hash.ZERO, mockParent.getHash()), + Optional.of(payloadParams), + ForkchoiceResult.withResult(Optional.empty(), Optional.of(mockHeader)), + VALID); + verify(engineCallListener, times(1)).executionEngineCalled(); + } + + private EngineUpdateForkchoiceResult assertSuccessWithPayloadForForkchoiceResult( + final EngineForkchoiceUpdatedParameter fcuParam, + final Optional payloadParam, + final ForkchoiceResult forkchoiceResult, + final EngineStatus expectedStatus) { + return assertSuccessWithPayloadForForkchoiceResult( + fcuParam, payloadParam, forkchoiceResult, expectedStatus, Optional.empty()); + } + + private EngineUpdateForkchoiceResult assertSuccessWithPayloadForForkchoiceResult( + final EngineForkchoiceUpdatedParameter fcuParam, + final Optional payloadParam, + final ForkchoiceResult forkchoiceResult, + final EngineStatus expectedStatus, + final Optional maybeLatestValidHash) { + + // result from mergeCoordinator has no new finalized, new head: + when(mergeCoordinator.updateForkChoice( + any(BlockHeader.class), any(Hash.class), any(Hash.class))) + .thenReturn(forkchoiceResult); + var resp = resp(fcuParam, payloadParam); + var res = fromSuccessResp(resp); + + assertThat(res.getPayloadStatus().getStatusAsString()).isEqualTo(expectedStatus.name()); + + if (expectedStatus.equals(VALID)) { + // check conditions when response is valid + assertThat(res.getPayloadStatus().getLatestValidHash()) + .isEqualTo(forkchoiceResult.getNewHead().map(BlockHeader::getBlockHash)); + assertThat(res.getPayloadStatus().getError()).isNullOrEmpty(); + if (payloadParam.isPresent()) { + assertThat(res.getPayloadId()).isNotNull(); + } else { + assertThat(res.getPayloadId()).isNull(); + } + } else { + // assert null latest valid and payload identifier: + assertThat(res.getPayloadStatus().getLatestValidHash()).isEqualTo(maybeLatestValidHash); + assertThat(res.getPayloadId()).isNull(); + } + + // assert that listeners are always notified + verify(mergeContext) + .fireNewUnverifiedForkchoiceEvent( + fcuParam.getHeadBlockHash(), + fcuParam.getSafeBlockHash(), + fcuParam.getFinalizedBlockHash()); + + verify(engineCallListener, times(1)).executionEngineCalled(); + + return res; + } + + private JsonRpcResponse resp( + final EngineForkchoiceUpdatedParameter forkchoiceParam, + final Optional payloadParam) { + return method.response( + new JsonRpcRequestContext( + new JsonRpcRequest( + "2.0", + RpcMethod.ENGINE_FORKCHOICE_UPDATED_V2.getMethodName(), + Stream.concat(Stream.of(forkchoiceParam), payloadParam.stream()).toArray()))); + } + + private EngineUpdateForkchoiceResult fromSuccessResp(final JsonRpcResponse resp) { + assertThat(resp.getType()).isEqualTo(JsonRpcResponseType.SUCCESS); + return Optional.of(resp) + .map(JsonRpcSuccessResponse.class::cast) + .map(JsonRpcSuccessResponse::getResult) + .map(EngineUpdateForkchoiceResult.class::cast) + .get(); + } + + private void assertInvalidForkchoiceState(final JsonRpcResponse resp) { + assertInvalidForkchoiceState(resp, JsonRpcError.INVALID_FORKCHOICE_STATE); + } + + private void assertInvalidForkchoiceState( + final JsonRpcResponse resp, final JsonRpcError jsonRpcError) { + assertThat(resp.getType()).isEqualTo(JsonRpcResponseType.ERROR); + + var errorResp = (JsonRpcErrorResponse) resp; + assertThat(errorResp.getError()).isEqualTo(jsonRpcError); + assertThat(errorResp.getError().getMessage()).isEqualTo(jsonRpcError.getMessage()); + } + + @Test + public void shouldReturnInvalidIfPayloadTimestampNotGreaterThanHead() { + var builder = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE); + BlockHeader mockParent = builder.number(9L).buildHeader(); + BlockHeader mockHeader = builder.number(10L).parentHash(mockParent.getHash()).buildHeader(); + when(blockchain.getBlockHeader(any())).thenReturn(Optional.of(mockHeader)); + when(mergeCoordinator.getOrSyncHeadByHash(mockHeader.getHash(), Hash.ZERO)) + .thenReturn(Optional.of(mockHeader)); + + when(mergeCoordinator.isDescendantOf(any(), any())).thenReturn(true); + when(mergeCoordinator.updateForkChoice( + any(BlockHeader.class), any(Hash.class), any(Hash.class))) + .thenReturn(ForkchoiceResult.withResult(Optional.of(mockParent), Optional.of(mockHeader))); + + final long timestampNotGreaterThanHead = mockHeader.getTimestamp(); + + var payloadParams = + new EnginePayloadAttributesParameterV2( + String.valueOf(timestampNotGreaterThanHead), + Bytes32.fromHexStringLenient("0xDEADBEEF").toHexString(), + Address.ECREC.toString(), + emptyList()); + + var resp = + resp( + new EngineForkchoiceUpdatedParameter( + mockHeader.getHash(), Hash.ZERO, mockParent.getHash()), + Optional.of(payloadParams)); + + assertInvalidForkchoiceState(resp, JsonRpcError.INVALID_PARAMS); + verify(engineCallListener, times(1)).executionEngineCalled(); + } +} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadTest.java index da53dd26746..224dc0926a3 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadTest.java @@ -62,7 +62,9 @@ public class EngineGetPayloadTest { private static final BlockHeader mockHeader = new BlockHeaderTestFixture().prevRandao(Bytes32.random()).buildHeader(); private static final Block mockBlock = - new Block(mockHeader, new BlockBody(Collections.emptyList(), Collections.emptyList())); + new Block( + mockHeader, + new BlockBody(Collections.emptyList(), Collections.emptyList(), Optional.empty())); @Mock private ProtocolContext protocolContext; diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV2Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV2Test.java new file mode 100644 index 00000000000..e5b6bd60455 --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV2Test.java @@ -0,0 +1,127 @@ +/* + * Copyright Hyperledger 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.api.jsonrpc.internal.methods.engine; + +import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.consensus.merge.MergeContext; +import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator; +import org.hyperledger.besu.consensus.merge.blockcreation.PayloadIdentifier; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineGetPayloadResultV2; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockBody; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; + +import java.util.Optional; + +import io.vertx.core.Vertx; +import org.apache.tuweni.bytes.Bytes32; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class EngineGetPayloadV2Test { + + private EngineGetPayloadV2 method; + private static final Vertx vertx = Vertx.vertx(); + private static final BlockResultFactory factory = new BlockResultFactory(); + private static final PayloadIdentifier mockPid = + PayloadIdentifier.forPayloadParams( + Hash.ZERO, 1337L, Bytes32.random(), Address.fromHexString("0x42")); + private static final BlockHeader mockHeader = + new BlockHeaderTestFixture().prevRandao(Bytes32.random()).buildHeader(); + private static final Block mockBlock = + new Block(mockHeader, new BlockBody(emptyList(), emptyList(), Optional.of(emptyList()))); + + @Mock private ProtocolContext protocolContext; + + @Mock private MergeContext mergeContext; + @Mock private MergeMiningCoordinator mergeMiningCoordinator; + + @Mock private EngineCallListener engineCallListener; + + @Before + public void before() { + when(mergeContext.retrieveBlockById(mockPid)).thenReturn(Optional.of(mockBlock)); + when(protocolContext.safeConsensusContext(Mockito.any())).thenReturn(Optional.of(mergeContext)); + this.method = + new EngineGetPayloadV2( + vertx, protocolContext, mergeMiningCoordinator, factory, engineCallListener); + } + + @Test + public void shouldReturnExpectedMethodName() { + // will break as specs change, intentional: + assertThat(method.getName()).isEqualTo("engine_getPayloadV2"); + } + + @Test + public void shouldReturnBlockForKnownPayloadId() { + final var resp = resp(mockPid); + assertThat(resp).isInstanceOf(JsonRpcSuccessResponse.class); + Optional.of(resp) + .map(JsonRpcSuccessResponse.class::cast) + .ifPresent( + r -> { + assertThat(r.getResult()).isInstanceOf(EngineGetPayloadResultV2.class); + final EngineGetPayloadResultV2 res = (EngineGetPayloadResultV2) r.getResult(); + assertThat(res.getExecutionPayload().getHash()) + .isEqualTo(mockHeader.getHash().toString()); + assertThat(res.getBlockValue()).isEqualTo(Quantity.create(0)); + assertThat(res.getExecutionPayload().getPrevRandao()) + .isEqualTo(mockHeader.getPrevRandao().map(Bytes32::toString).orElse("")); + }); + verify(engineCallListener, times(1)).executionEngineCalled(); + } + + @Test + public void shouldFailForUnknownPayloadId() { + final var resp = + resp( + PayloadIdentifier.forPayloadParams( + Hash.ZERO, 0L, Bytes32.random(), Address.fromHexString("0x42"))); + assertThat(resp).isInstanceOf(JsonRpcErrorResponse.class); + verify(engineCallListener, times(1)).executionEngineCalled(); + } + + private JsonRpcResponse resp(final PayloadIdentifier pid) { + return method.response( + new JsonRpcRequestContext( + new JsonRpcRequest( + "2.0", + RpcMethod.ENGINE_GET_PAYLOAD_V2.getMethodName(), + new Object[] {pid.serialize()}))); + } +} 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 06f6cbdb090..2352da9cce5 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 @@ -172,7 +172,9 @@ public void shouldReturnAcceptedOnLatestValidAncestorEmpty() { public void shouldReturnSuccessOnAlreadyPresent() { BlockHeader mockHeader = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE).buildHeader(); Block mockBlock = - new Block(mockHeader, new BlockBody(Collections.emptyList(), Collections.emptyList())); + new Block( + mockHeader, + new BlockBody(Collections.emptyList(), Collections.emptyList(), Optional.empty())); when(blockchain.getBlockByHash(any())).thenReturn(Optional.of(mockBlock)); @@ -433,7 +435,8 @@ private EnginePayloadParameter mockPayload(final BlockHeader header, final List< header.getReceiptsRoot(), header.getLogsBloom(), header.getPrevRandao().map(Bytes32::toHexString).orElse("0x0"), - txs); + txs, + Collections.emptyList()); } private EnginePayloadStatusResult fromSuccessResp(final JsonRpcResponse resp) { diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracerTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracerTest.java index 833dfefc35e..1215a898c1b 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracerTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracerTest.java @@ -113,7 +113,7 @@ public void setUp() throws Exception { when(previousBlockHeader.getStateRoot()).thenReturn(Hash.ZERO); when(worldStateArchive.getMutable(Hash.ZERO, null, false)) .thenReturn(Optional.of(mutableWorldState)); - when(protocolSchedule.getByBlockNumber(12)).thenReturn(protocolSpec); + when(protocolSchedule.getByBlockHeader(blockHeader)).thenReturn(protocolSpec); when(protocolSpec.getTransactionProcessor()).thenReturn(transactionProcessor); when(protocolSpec.getMiningBeneficiaryCalculator()).thenReturn(BlockHeader::getCoinbase); when(blockchain.getChainHeadHeader()).thenReturn(blockHeader); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/flat/RewardTraceGeneratorTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/flat/RewardTraceGeneratorTest.java index 6c02d365971..89ab1e0ee73 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/flat/RewardTraceGeneratorTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/flat/RewardTraceGeneratorTest.java @@ -68,7 +68,8 @@ public class RewardTraceGeneratorTest { @Before public void setUp() { - final BlockBody blockBody = new BlockBody(Collections.emptyList(), List.of(ommerHeader)); + final BlockBody blockBody = + new BlockBody(Collections.emptyList(), List.of(ommerHeader), Optional.empty()); final BlockHeader blockHeader = gen.header(0x0A, blockBody, new BlockDataGenerator.BlockOptions()); block = new Block(blockHeader, blockBody); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueriesLogCacheTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueriesLogCacheTest.java index 33b82e3a18c..e0b5a31972b 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueriesLogCacheTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueriesLogCacheTest.java @@ -110,9 +110,11 @@ public void setup() { null, Hash.EMPTY, 0, + Hash.EMPTY, new MainnetBlockHeaderFunctions()); testHash = fakeHeader.getHash(); - final BlockBody fakeBody = new BlockBody(Collections.emptyList(), Collections.emptyList()); + final BlockBody fakeBody = + new BlockBody(Collections.emptyList(), Collections.emptyList(), Optional.empty()); when(blockchain.getBlockHashByNumber(anyLong())).thenReturn(Optional.of(testHash)); when(blockchain.getBlockHeader(any())).thenReturn(Optional.of(fakeHeader)); when(blockchain.getBlockHeader(anyLong())).thenReturn(Optional.of(fakeHeader)); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/GoQuorumPrivateTxBloomBlockchainQueriesTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/GoQuorumPrivateTxBloomBlockchainQueriesTest.java index 4770214e20f..a125837f4d8 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/GoQuorumPrivateTxBloomBlockchainQueriesTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/GoQuorumPrivateTxBloomBlockchainQueriesTest.java @@ -92,10 +92,12 @@ public void setup() { null, Hash.EMPTY, 0, + Hash.EMPTY, new MainnetBlockHeaderFunctions(), Optional.of(testLogsBloomFilter)); testHash = fakeHeader.getHash(); - final BlockBody fakeBody = new BlockBody(Collections.emptyList(), Collections.emptyList()); + final BlockBody fakeBody = + new BlockBody(Collections.emptyList(), Collections.emptyList(), Optional.empty()); when(blockchain.getBlockHeader(any())).thenReturn(Optional.of(fakeHeader)); when(blockchain.getBlockHeader(anyLong())).thenReturn(Optional.of(fakeHeader)); when(blockchain.getTxReceipts(any())).thenReturn(Optional.of(Collections.emptyList())); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/cache/GoQuorumPrivateTransactionLogBloomCacherTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/cache/GoQuorumPrivateTransactionLogBloomCacherTest.java index d84aeb9b22b..c8ed94709c8 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/cache/GoQuorumPrivateTransactionLogBloomCacherTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/cache/GoQuorumPrivateTransactionLogBloomCacherTest.java @@ -107,7 +107,8 @@ public void setup() throws IOException { @Test public void shouldUpdateCacheWhenBlockAdded() throws IOException { - final BlockBody fakeBody = new BlockBody(Collections.emptyList(), Collections.emptyList()); + final BlockBody fakeBody = + new BlockBody(Collections.emptyList(), Collections.emptyList(), Optional.empty()); when(blockchain.getBlockHeader(testBlockHeaderHash)).thenReturn(Optional.of(fakeHeader)); when(blockchain.getBlockHashByNumber(anyLong())).thenReturn(Optional.of(testBlockHeaderHash)); when(blockchain.getTxReceipts(any())).thenReturn(Optional.of(Collections.emptyList())); @@ -168,6 +169,7 @@ private BlockHeader createBlock(final long number) { null, Hash.EMPTY, 0, + Hash.EMPTY, new MainnetBlockHeaderFunctions(), Optional.of(testLogsBloomFilter)); return fakeHeader; diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/cache/TransactionLogBloomCacherTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/cache/TransactionLogBloomCacherTest.java index 32352886b35..2a08e39200c 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/cache/TransactionLogBloomCacherTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/cache/TransactionLogBloomCacherTest.java @@ -100,6 +100,7 @@ public void setup() throws IOException { null, Hash.EMPTY, 0, + Hash.EMPTY, new MainnetBlockHeaderFunctions()); testHash = fakeHeader.getHash(); when(blockchain.getBlockHeader(anyLong())).thenReturn(Optional.of(fakeHeader)); @@ -264,6 +265,7 @@ private BlockHeader createBlock(final long number, final Optional messag null, Hash.EMPTY, 0, + Hash.EMPTY, new MainnetBlockHeaderFunctions()); testHash = fakeHeader.getHash(); when(blockchain.getBlockHeader(number)).thenReturn(Optional.of(fakeHeader)); diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java index aa6b27d95c5..9a2a66d4a53 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java @@ -28,6 +28,7 @@ import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.core.SealableBlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter; import org.hyperledger.besu.ethereum.mainnet.AbstractBlockProcessor; import org.hyperledger.besu.ethereum.mainnet.BodyValidation; @@ -36,6 +37,7 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions; +import org.hyperledger.besu.ethereum.mainnet.WithdrawalsProcessor; import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.evm.account.EvmAccount; @@ -139,12 +141,14 @@ public BlockCreationResult createBlock( final Optional> maybeTransactions, final Optional> maybeOmmers, final long timestamp) { - return createBlock(maybeTransactions, maybeOmmers, Optional.empty(), timestamp, true); + return createBlock( + maybeTransactions, maybeOmmers, Optional.empty(), Optional.empty(), timestamp, true); } protected BlockCreationResult createBlock( final Optional> maybeTransactions, final Optional> maybeOmmers, + final Optional> maybeWithdrawals, final Optional maybePrevRandao, final long timestamp, boolean rewardCoinbase) { @@ -168,7 +172,7 @@ protected BlockCreationResult createBlock( throwIfStopped(); final ProtocolSpec newProtocolSpec = - protocolSchedule.getByBlockNumber(processableBlockHeader.getNumber()); + protocolSchedule.getByBlockHeader(processableBlockHeader); if (rewardCoinbase && !rewardBeneficiary( @@ -184,6 +188,16 @@ protected BlockCreationResult createBlock( throwIfStopped(); + maybeWithdrawals.ifPresent( + withdrawals -> { + WithdrawalsProcessor withdrawalsProcessor = newProtocolSpec.getWithdrawalsProcessor(); + final WorldUpdater updater = disposableWorldState.updater(); + for (Withdrawal withdrawal : withdrawals) { + withdrawalsProcessor.processWithdrawal(withdrawal, updater); + } + updater.commit(); + }); + final SealableBlockHeader sealableBlockHeader = BlockHeaderBuilder.create() .populateFrom(processableBlockHeader) @@ -195,12 +209,15 @@ protected BlockCreationResult createBlock( .logsBloom(BodyValidation.logsBloom(transactionResults.getReceipts())) .gasUsed(transactionResults.getCumulativeGasUsed()) .extraData(extraDataCalculator.get(parentHeader)) + .withdrawalsRoot( + maybeWithdrawals.map(BodyValidation::withdrawalsRoot).orElse(Hash.EMPTY)) .buildSealableBlockHeader(); final BlockHeader blockHeader = createFinalBlockHeader(sealableBlockHeader); - final Block block = - new Block(blockHeader, new BlockBody(transactionResults.getTransactions(), ommers)); + new Block( + blockHeader, + new BlockBody(transactionResults.getTransactions(), ommers, maybeWithdrawals)); return new BlockCreationResult(block, transactionResults); } catch (final SecurityModuleException ex) { throw new IllegalStateException("Failed to create block signature", ex); diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractMiningCoordinatorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractMiningCoordinatorTest.java index 623f612fcf4..ea3ccc1145c 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractMiningCoordinatorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractMiningCoordinatorTest.java @@ -42,7 +42,7 @@ public class AbstractMiningCoordinatorTest { private static final Block BLOCK = new Block( new BlockHeaderTestFixture().buildHeader(), - new BlockBody(Collections.emptyList(), Collections.emptyList())); + new BlockBody(Collections.emptyList(), Collections.emptyList(), Optional.empty())); private final Blockchain blockchain = mock(Blockchain.class); private final PoWMinerExecutor minerExecutor = mock(PoWMinerExecutor.class); private final SyncState syncState = mock(SyncState.class); diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/BlockMinerTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/BlockMinerTest.java index 1b88bdc249c..901bea79882 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/BlockMinerTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/BlockMinerTest.java @@ -52,7 +52,8 @@ public void blockCreatedIsAddedToBlockChain() throws InterruptedException { final Block blockToCreate = new Block( - headerBuilder.buildHeader(), new BlockBody(Lists.newArrayList(), Lists.newArrayList())); + headerBuilder.buildHeader(), + new BlockBody(Lists.newArrayList(), Lists.newArrayList(), Optional.empty())); final ProtocolContext protocolContext = new ProtocolContext(null, null, null); @@ -93,7 +94,8 @@ public void failureToImportDoesNotTriggerObservers() throws InterruptedException final Block blockToCreate = new Block( - headerBuilder.buildHeader(), new BlockBody(Lists.newArrayList(), Lists.newArrayList())); + headerBuilder.buildHeader(), + new BlockBody(Lists.newArrayList(), Lists.newArrayList(), Optional.empty())); final ProtocolContext protocolContext = new ProtocolContext(null, null, null); diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/OperationBenchmarkHelper.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/OperationBenchmarkHelper.java index f6bba6a165f..a57ea8eb990 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/OperationBenchmarkHelper.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/OperationBenchmarkHelper.java @@ -34,6 +34,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Optional; import com.google.common.io.MoreFiles; import com.google.common.io.RecursiveDeleteOption; @@ -76,7 +77,7 @@ public static OperationBenchmarkHelper create() throws IOException { .number(i) .difficulty(Difficulty.ONE) .buildHeader(), - new BlockBody(emptyList(), emptyList())), + new BlockBody(emptyList(), emptyList(), Optional.empty())), emptyList()); } final MessageFrame messageFrame = diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java index 4c32d7a4fe2..181f87f39e7 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java @@ -41,6 +41,8 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; +import java.util.OptionalLong; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -52,7 +54,7 @@ public final class GenesisState { private static final BlockBody BODY = - new BlockBody(Collections.emptyList(), Collections.emptyList()); + new BlockBody(Collections.emptyList(), Collections.emptyList(), Optional.empty()); private final Block block; private final List genesisAccounts; @@ -155,6 +157,7 @@ private static BlockHeader buildHeader( .nonce(parseNonce(genesis)) .blockHeaderFunctions(ScheduleBasedBlockHeaderFunctions.create(protocolSchedule)) .baseFee(genesis.getGenesisBaseFeePerGas().orElse(null)) + .withdrawalsRoot(isShanghaiAtGenesis(genesis) ? Hash.EMPTY_TRIE_HASH : Hash.EMPTY) .buildBlockHeader(); } @@ -212,6 +215,14 @@ private static long parseUnsignedLong(final String value) { return Long.parseUnsignedLong(nonce, 16); } + private static boolean isShanghaiAtGenesis(final GenesisConfigFile genesis) { + final OptionalLong shanghaiTimestamp = genesis.getConfigOptions().getShanghaiTime(); + if (shanghaiTimestamp.isPresent()) { + return shanghaiTimestamp.getAsLong() == genesis.getTimestamp(); + } + return false; + } + @Override public String toString() { return MoreObjects.toStringHelper(this) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Block.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Block.java index 09ea67f2fe8..72e86bdb446 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Block.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Block.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Objects; +import java.util.Optional; import org.apache.tuweni.bytes.Bytes; @@ -61,6 +62,8 @@ public void writeTo(final RLPOutput out) { out.writeList(body.getTransactions(), Transaction::writeTo); out.writeList(body.getOmmers(), BlockHeader::writeTo); + body.getWithdrawals().ifPresent(withdrawals -> out.writeList(withdrawals, Withdrawal::writeTo)); + out.endList(); } @@ -69,9 +72,11 @@ public static Block readFrom(final RLPInput in, final BlockHeaderFunctions hashF final BlockHeader header = BlockHeader.readFrom(in, hashFunction); final List transactions = in.readList(Transaction::readFrom); final List ommers = in.readList(rlp -> BlockHeader.readFrom(rlp, hashFunction)); + final Optional> withdrawals = + in.isEndOfCurrentList() ? Optional.empty() : Optional.of(in.readList(Withdrawal::readFrom)); in.leaveList(); - return new Block(header, new BlockBody(transactions, ommers)); + return new Block(header, new BlockBody(transactions, ommers, withdrawals)); } @Override diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockBody.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockBody.java index 8ae7024b32d..42826e56a16 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockBody.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockBody.java @@ -20,18 +20,28 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Optional; public class BlockBody implements org.hyperledger.besu.plugin.data.BlockBody { private static final BlockBody EMPTY = - new BlockBody(Collections.emptyList(), Collections.emptyList()); - + new BlockBody(Collections.emptyList(), Collections.emptyList(), Optional.empty()); + /** + * Adding a new field with a corresponding root hash in the block header will require a change in + * {@link org.hyperledger.besu.ethereum.eth.manager.task.GetBodiesFromPeerTask.BodyIdentifier } + */ private final List transactions; + private final List ommers; + private final Optional> maybeWithdrawals; - public BlockBody(final List transactions, final List ommers) { + public BlockBody( + final List transactions, + final List ommers, + final Optional> withdrawals) { this.transactions = transactions; this.ommers = ommers; + this.maybeWithdrawals = withdrawals; } public static BlockBody empty() { @@ -50,6 +60,10 @@ public List getOmmers() { return ommers; } + public Optional> getWithdrawals() { + return maybeWithdrawals; + } + /** * Writes Block to {@link RLPOutput}. * @@ -61,6 +75,8 @@ public void writeTo(final RLPOutput output) { output.writeList(getTransactions(), Transaction::writeTo); output.writeList(getOmmers(), BlockHeader::writeTo); + maybeWithdrawals.ifPresent(withdrawals -> output.writeList(withdrawals, Withdrawal::writeTo)); + output.endList(); } @@ -71,7 +87,10 @@ public static BlockBody readFrom( final BlockBody body = new BlockBody( input.readList(Transaction::readFrom), - input.readList(rlp -> BlockHeader.readFrom(rlp, blockHeaderFunctions))); + input.readList(rlp -> BlockHeader.readFrom(rlp, blockHeaderFunctions)), + input.isEndOfCurrentList() + ? Optional.empty() + : Optional.of(input.readList(Withdrawal::readFrom))); input.leaveList(); return body; } @@ -85,12 +104,14 @@ public boolean equals(final Object obj) { return false; } final BlockBody other = (BlockBody) obj; - return transactions.equals(other.transactions) && ommers.equals(other.ommers); + return transactions.equals(other.transactions) + && ommers.equals(other.ommers) + && maybeWithdrawals.equals(other.maybeWithdrawals); } @Override public int hashCode() { - return Objects.hash(transactions, ommers); + return Objects.hash(transactions, ommers, maybeWithdrawals); } @Override @@ -98,7 +119,8 @@ public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("BlockBody{"); sb.append("transactions=").append(transactions).append(", "); - sb.append("ommers=").append(ommers); + sb.append("ommers=").append(ommers).append(", "); + sb.append("withdrawals=").append(maybeWithdrawals); return sb.append("}").toString(); } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeader.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeader.java index 5fed4dfb2ed..acf8c0f65c9 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeader.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeader.java @@ -62,6 +62,7 @@ public BlockHeader( final Wei baseFee, final Bytes32 mixHashOrPrevRandao, final long nonce, + final Hash withdrawalHashRoot, final BlockHeaderFunctions blockHeaderFunctions, final Optional privateLogsBloom) { super( @@ -79,7 +80,8 @@ public BlockHeader( timestamp, extraData, baseFee, - mixHashOrPrevRandao); + mixHashOrPrevRandao, + withdrawalHashRoot); this.nonce = nonce; this.hash = Suppliers.memoize(() -> blockHeaderFunctions.hash(this)); this.parsedExtraData = Suppliers.memoize(() -> blockHeaderFunctions.parseExtraData(this)); @@ -103,6 +105,7 @@ public BlockHeader( final Wei baseFee, final Bytes32 mixHashOrPrevRandao, final long nonce, + final Hash withdrawalHashRoot, final BlockHeaderFunctions blockHeaderFunctions) { super( parentHash, @@ -119,7 +122,8 @@ public BlockHeader( timestamp, extraData, baseFee, - mixHashOrPrevRandao); + mixHashOrPrevRandao, + withdrawalHashRoot); this.nonce = nonce; this.hash = Suppliers.memoize(() -> blockHeaderFunctions.hash(this)); this.parsedExtraData = Suppliers.memoize(() -> blockHeaderFunctions.parseExtraData(this)); @@ -225,6 +229,9 @@ public void writeTo(final RLPOutput out) { if (baseFee != null) { out.writeUInt256Scalar(baseFee); } + if (!withdrawalRoot.equals(Hash.EMPTY)) { + out.writeBytes(withdrawalRoot); + } out.endList(); } @@ -247,6 +254,8 @@ public static BlockHeader readFrom( final Bytes32 mixHashOrPrevRandao = input.readBytes32(); final long nonce = input.readLong(); final Wei baseFee = !input.isEndOfCurrentList() ? Wei.of(input.readUInt256Scalar()) : null; + final Hash withdrawalHashRoot = + !input.isEndOfCurrentList() ? Hash.wrap(input.readBytes32()) : Hash.EMPTY; input.leaveList(); return new BlockHeader( parentHash, @@ -265,6 +274,7 @@ public static BlockHeader readFrom( baseFee, mixHashOrPrevRandao, nonce, + withdrawalHashRoot, blockHeaderFunctions); } @@ -305,7 +315,8 @@ public String toString() { sb.append("extraData=").append(extraData).append(", "); sb.append("baseFee=").append(baseFee).append(", "); sb.append("mixHashOrPrevRandao=").append(mixHashOrPrevRandao).append(", "); - sb.append("nonce=").append(nonce); + sb.append("nonce=").append(nonce).append(", "); + sb.append("withdrawalsRoot=").append(withdrawalRoot); return sb.append("}").toString(); } @@ -329,9 +340,11 @@ public static org.hyperledger.besu.ethereum.core.BlockHeader convertPluginBlockH pluginBlockHeader.getBaseFee().map(Wei::fromQuantity).orElse(null), pluginBlockHeader.getPrevRandao().orElse(null), pluginBlockHeader.getNonce(), + Hash.fromHexString(pluginBlockHeader.getWithdrawalRoot().toHexString()), blockHeaderFunctions); } + @Override public String toLogString() { return getNumber() + " (" + getHash() + ")"; } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeaderBuilder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeaderBuilder.java index d285b778845..d861af272bc 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeaderBuilder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeaderBuilder.java @@ -64,6 +64,8 @@ public class BlockHeaderBuilder { private BlockHeaderFunctions blockHeaderFunctions; + private Hash withdrawalsRoot = Hash.EMPTY; + // A nonce can be any value so we use the OptionalLong // instead of an invalid identifier such as -1. private OptionalLong nonce = OptionalLong.empty(); @@ -111,11 +113,18 @@ public static BlockHeaderBuilder fromBuilder(final BlockHeaderBuilder fromBuilde .extraData(fromBuilder.extraData) .baseFee(fromBuilder.baseFee) .prevRandao(fromBuilder.mixHashOrPrevRandao) - .blockHeaderFunctions(fromBuilder.blockHeaderFunctions); + .blockHeaderFunctions(fromBuilder.blockHeaderFunctions) + .withdrawalsRoot(fromBuilder.withdrawalsRoot); toBuilder.nonce = fromBuilder.nonce; return toBuilder; } + public BlockHeaderBuilder withdrawalsRoot(final Hash hash) { + checkNotNull(hash); + this.withdrawalsRoot = hash; + return this; + } + public BlockHeader buildBlockHeader() { validateBlockHeader(); @@ -136,6 +145,7 @@ public BlockHeader buildBlockHeader() { baseFee, mixHashOrPrevRandao, nonce.getAsLong(), + withdrawalsRoot, blockHeaderFunctions); } @@ -171,7 +181,8 @@ public SealableBlockHeader buildSealableBlockHeader() { timestamp, extraData, baseFee, - mixHashOrPrevRandao); + mixHashOrPrevRandao, + withdrawalsRoot); } private void validateBlockHeader() { @@ -231,6 +242,7 @@ public BlockHeaderBuilder populateFrom(final SealableBlockHeader sealableBlockHe extraData(sealableBlockHeader.getExtraData()); baseFee(sealableBlockHeader.getBaseFee().orElse(null)); sealableBlockHeader.getPrevRandao().ifPresent(this::prevRandao); + withdrawalsRoot(sealableBlockHeader.getWithdrawalsRoot()); return this; } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java index 4f2492c788f..319044db37d 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java @@ -158,4 +158,8 @@ public Bytes32 getMixHashOrPrevRandao() { public Optional getPrevRandao() { return Optional.ofNullable(mixHashOrPrevRandao); } + + public String toLogString() { + return getNumber() + " (time: " + getTimestamp() + ")"; + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/SealableBlockHeader.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/SealableBlockHeader.java index 07a9e8ba0d5..707ca84ae13 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/SealableBlockHeader.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/SealableBlockHeader.java @@ -37,6 +37,7 @@ public class SealableBlockHeader extends ProcessableBlockHeader { protected final long gasUsed; protected final Bytes extraData; + protected final Hash withdrawalRoot; protected SealableBlockHeader( final Hash parentHash, @@ -53,7 +54,8 @@ protected SealableBlockHeader( final long timestamp, final Bytes extraData, final Wei baseFee, - final Bytes32 mixHashOrPrevRandao) { + final Bytes32 mixHashOrPrevRandao, + final Hash withdrawalRoot) { super( parentHash, coinbase, @@ -70,6 +72,7 @@ protected SealableBlockHeader( this.logsBloom = logsBloom; this.gasUsed = gasUsed; this.extraData = extraData; + this.withdrawalRoot = withdrawalRoot; } /** @@ -134,4 +137,12 @@ public long getGasUsed() { public Bytes getExtraData() { return extraData; } + + public Hash getWithdrawalRoot() { + return withdrawalRoot; + } + + public Hash getWithdrawalsRoot() { + return withdrawalRoot; + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Withdrawal.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Withdrawal.java new file mode 100644 index 00000000000..60781bfe494 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Withdrawal.java @@ -0,0 +1,101 @@ +/* + * + * * Copyright Hyperledger 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.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.GWei; +import org.hyperledger.besu.ethereum.core.encoding.WithdrawalDecoder; +import org.hyperledger.besu.ethereum.core.encoding.WithdrawalEncoder; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.rlp.RLPInput; +import org.hyperledger.besu.ethereum.rlp.RLPOutput; + +import java.util.Objects; +import java.util.StringJoiner; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.units.bigints.UInt64; + +public class Withdrawal { + private final UInt64 index; + private final UInt64 validatorIndex; + private final Address address; + private final GWei amount; + + public Withdrawal( + final UInt64 index, final UInt64 validatorIndex, final Address address, final GWei amount) { + this.index = index; + this.validatorIndex = validatorIndex; + this.address = address; + this.amount = amount; + } + + public static Withdrawal readFrom(final Bytes rlpBytes) { + return readFrom(RLP.input(rlpBytes)); + } + + public static Withdrawal readFrom(final RLPInput rlpInput) { + return WithdrawalDecoder.decode(rlpInput); + } + + public UInt64 getIndex() { + return index; + } + + public Address getAddress() { + return address; + } + + public GWei getAmount() { + return amount; + } + + public UInt64 getValidatorIndex() { + return validatorIndex; + } + + public void writeTo(final RLPOutput out) { + WithdrawalEncoder.encode(this, out); + } + + @Override + public String toString() { + return new StringJoiner(", ", Withdrawal.class.getSimpleName() + "[", "]") + .add("index=" + index) + .add("validatorIndex=" + validatorIndex) + .add("address=" + address) + .add("amount=" + amount) + .toString(); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final Withdrawal that = (Withdrawal) o; + return Objects.equals(index, that.index) + && Objects.equals(validatorIndex, that.validatorIndex) + && Objects.equals(address, that.address) + && Objects.equals(amount, that.amount); + } + + @Override + public int hashCode() { + return Objects.hash(index, validatorIndex, address, amount); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalDecoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalDecoder.java new file mode 100644 index 00000000000..b153af8740a --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalDecoder.java @@ -0,0 +1,45 @@ +/* + * + * * Copyright Hyperledger 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.encoding; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.GWei; +import org.hyperledger.besu.ethereum.core.Withdrawal; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.rlp.RLPInput; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.units.bigints.UInt64; + +public class WithdrawalDecoder { + + public static Withdrawal decode(final RLPInput rlpInput) { + rlpInput.enterList(); + final UInt64 index = rlpInput.readUInt64Scalar(); + final UInt64 validatorIndex = rlpInput.readUInt64Scalar(); + final Address address = Address.readFrom(rlpInput); + final GWei amount = GWei.of(rlpInput.readUInt64Scalar()); + rlpInput.leaveList(); + + return new Withdrawal(index, validatorIndex, address, amount); + } + + public static Withdrawal decodeOpaqueBytes(final Bytes input) { + return decode(RLP.input(input)); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalEncoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalEncoder.java new file mode 100644 index 00000000000..6ce4d659c16 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalEncoder.java @@ -0,0 +1,40 @@ +/* + * + * * Copyright Hyperledger 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.encoding; + +import org.hyperledger.besu.ethereum.core.Withdrawal; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.rlp.RLPOutput; + +import org.apache.tuweni.bytes.Bytes; + +public class WithdrawalEncoder { + public static Bytes encodeOpaqueBytes(final Withdrawal withdrawal) { + + return RLP.encode(rlpOutput -> encode(withdrawal, rlpOutput)); + } + + public static void encode(final Withdrawal withdrawal, final RLPOutput rlpOutput) { + rlpOutput.startList(); + rlpOutput.writeUInt64Scalar(withdrawal.getIndex()); + rlpOutput.writeUInt64Scalar(withdrawal.getValidatorIndex()); + rlpOutput.writeBytes(withdrawal.getAddress()); + rlpOutput.writeUInt64Scalar(withdrawal.getAmount()); + rlpOutput.endList(); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/forkid/ForkIdManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/forkid/ForkIdManager.java index cb81a164c3e..d9d8900fc40 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/forkid/ForkIdManager.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/forkid/ForkIdManager.java @@ -19,12 +19,14 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.rlp.RLPInput; +import org.hyperledger.besu.plugin.data.BlockHeader; import org.hyperledger.besu.util.EndianUtils; import java.util.ArrayList; import java.util.List; -import java.util.function.LongSupplier; +import java.util.function.Supplier; import java.util.stream.Collectors; +import java.util.stream.Stream; import java.util.zip.CRC32; import com.google.common.annotations.VisibleForTesting; @@ -34,10 +36,13 @@ public class ForkIdManager { private final Hash genesisHash; - private final List forkIds; + private final List blockNumbersForkIds; + private final List timestampsForkIds; - private final List forkBlockNumbers; - private final LongSupplier chainHeadSupplier; + private final List blockNumberForks; + private final List timestampForks; + + private final Supplier chainHeadSupplier; private final long forkNext; private final boolean onlyZerosForkBlocks; private final long highestKnownFork; @@ -45,41 +50,73 @@ public class ForkIdManager { private final boolean legacyEth64; public ForkIdManager( - final Blockchain blockchain, final List nonFilteredForks, final boolean legacyEth64) { + final Blockchain blockchain, + final List blockNumberForks, + final List timestampForks, + final boolean legacyEth64) { checkNotNull(blockchain); - checkNotNull(nonFilteredForks); - this.chainHeadSupplier = blockchain::getChainHeadBlockNumber; + checkNotNull(blockNumberForks); + this.chainHeadSupplier = blockchain::getChainHeadHeader; this.genesisHash = blockchain.getGenesisBlock().getHash(); - this.forkIds = new ArrayList<>(); + this.blockNumbersForkIds = new ArrayList<>(); + this.timestampsForkIds = new ArrayList<>(); this.legacyEth64 = legacyEth64; - this.forkBlockNumbers = - nonFilteredForks.stream() + this.blockNumberForks = + blockNumberForks.stream() .filter(fork -> fork > 0L) .distinct() .sorted() .collect(Collectors.toUnmodifiableList()); - this.onlyZerosForkBlocks = nonFilteredForks.stream().allMatch(value -> 0L == value); + this.timestampForks = + timestampForks.stream() + .filter(fork -> fork > 0L) + .distinct() + .sorted() + .collect(Collectors.toUnmodifiableList()); + this.onlyZerosForkBlocks = + Stream.concat(blockNumberForks.stream(), timestampForks.stream()) + .allMatch(value -> 0L == value); this.forkNext = createForkIds(); + final long highestKnownBlockFork = + !blockNumberForks.isEmpty() ? blockNumberForks.get(blockNumberForks.size() - 1) : 0L; this.highestKnownFork = - !forkBlockNumbers.isEmpty() ? forkBlockNumbers.get(forkBlockNumbers.size() - 1) : 0L; + !timestampForks.isEmpty() + ? timestampForks.get(timestampForks.size() - 1) + : highestKnownBlockFork; } public ForkId getForkIdForChainHead() { if (legacyEth64) { - return forkIds.isEmpty() ? null : forkIds.get(forkIds.size() - 1); + return blockNumbersForkIds.isEmpty() + ? null + : blockNumbersForkIds.get(blockNumbersForkIds.size() - 1); } - final long head = chainHeadSupplier.getAsLong(); - for (final ForkId forkId : forkIds) { - if (head < forkId.getNext()) { + final BlockHeader header = chainHeadSupplier.get(); + for (final ForkId forkId : blockNumbersForkIds) { + if (header.getNumber() < forkId.getNext()) { return forkId; } } - return forkIds.isEmpty() ? new ForkId(genesisHashCrc, 0) : forkIds.get(forkIds.size() - 1); + for (final ForkId forkId : timestampsForkIds) { + if (header.getTimestamp() < forkId.getNext()) { + return forkId; + } + } + final ForkId blockNumberForkId = + blockNumbersForkIds.isEmpty() + ? new ForkId(genesisHashCrc, 0) + : blockNumbersForkIds.get(blockNumbersForkIds.size() - 1); + return timestampsForkIds.isEmpty() + ? blockNumberForkId + : timestampsForkIds.get(timestampsForkIds.size() - 1); } @VisibleForTesting - public List getForkIds() { - return this.forkIds; + public List getAllForkIds() { + List forkIds = new ArrayList<>(timestampsForkIds.size() + blockNumbersForkIds.size()); + forkIds.addAll(blockNumbersForkIds); + forkIds.addAll(timestampsForkIds); + return forkIds; } public static ForkId readFrom(final RLPInput in) { @@ -119,7 +156,7 @@ public boolean peerCheck(final ForkId forkId) { if (!isHashKnown(forkId.getHash())) { return false; } - return chainHeadSupplier.getAsLong() < forkNext + return chainHeadSupplier.get().getNumber() < forkNext || (isForkKnown(forkId.getNext()) && isRemoteAwareOfPresent(forkId.getHash(), forkId.getNext())); } @@ -135,16 +172,20 @@ public boolean peerCheck(final Bytes32 peerGenesisHash) { } private boolean isHashKnown(final Bytes forkHash) { - return forkIds.stream().map(ForkId::getHash).anyMatch(hash -> hash.equals(forkHash)); + return Stream.concat(blockNumbersForkIds.stream(), timestampsForkIds.stream()) + .map(ForkId::getHash) + .anyMatch(hash -> hash.equals(forkHash)); } private boolean isForkKnown(final Long nextFork) { return highestKnownFork < nextFork - || forkIds.stream().map(ForkId::getNext).anyMatch(fork -> fork.equals(nextFork)); + || Stream.concat(blockNumbersForkIds.stream(), timestampsForkIds.stream()) + .map(ForkId::getNext) + .anyMatch(fork -> fork.equals(nextFork)); } private boolean isRemoteAwareOfPresent(final Bytes forkHash, final Long nextFork) { - for (final ForkId j : forkIds) { + for (final ForkId j : getAllForkIds()) { if (forkHash.equals(j.getHash())) { if (nextFork.equals(j.getNext())) { return true; @@ -162,21 +203,34 @@ private long createForkIds() { final CRC32 crc = new CRC32(); crc.update(genesisHash.toArray()); genesisHashCrc = getCurrentCrcHash(crc); - final List forkHashes = new ArrayList<>(List.of(genesisHashCrc)); - forkBlockNumbers.forEach( + final List numberForkHashes = new ArrayList<>(List.of(genesisHashCrc)); + blockNumberForks.forEach( + fork -> { + updateCrc(crc, fork); + numberForkHashes.add(getCurrentCrcHash(crc)); + }); + + timestampForks.forEach( fork -> { updateCrc(crc, fork); - forkHashes.add(getCurrentCrcHash(crc)); + numberForkHashes.add(getCurrentCrcHash(crc)); }); // This loop is for all the fork hashes that have an associated "next fork" - for (int i = 0; i < forkBlockNumbers.size(); i++) { - forkIds.add(new ForkId(forkHashes.get(i), forkBlockNumbers.get(i))); + for (int i = 0; i < blockNumberForks.size(); i++) { + blockNumbersForkIds.add(new ForkId(numberForkHashes.get(i), blockNumberForks.get(i))); + } + for (int i = 0; i < timestampForks.size(); i++) { + timestampsForkIds.add( + new ForkId(numberForkHashes.get(blockNumberForks.size() + i), timestampForks.get(i))); } long forkNext = 0; - if (!forkBlockNumbers.isEmpty()) { - forkNext = forkIds.get(forkIds.size() - 1).getNext(); - forkIds.add(new ForkId(forkHashes.get(forkHashes.size() - 1), 0)); + if (!timestampForks.isEmpty()) { + forkNext = timestampForks.get(timestampForks.size() - 1); + timestampsForkIds.add(new ForkId(numberForkHashes.get(numberForkHashes.size() - 1), 0)); + } else if (!blockNumberForks.isEmpty()) { + forkNext = blockNumbersForkIds.get(blockNumbersForkIds.size() - 1).getNext(); + blockNumbersForkIds.add(new ForkId(numberForkHashes.get(numberForkHashes.size() - 1), 0)); } return forkNext; } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java index 02ae5cae2fe..63d25ccf7b6 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java @@ -25,6 +25,7 @@ import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; +import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.vm.BlockHashLookup; @@ -58,6 +59,8 @@ TransactionReceipt create( static final int MAX_GENERATION = 6; protected final MainnetTransactionProcessor transactionProcessor; + protected final WithdrawalsProcessor withdrawalsProcessor = + new WithdrawalsProcessor.AllowedWithdrawalsProcessor(); protected final AbstractBlockProcessor.TransactionReceiptFactory transactionReceiptFactory; @@ -87,6 +90,7 @@ public BlockProcessingResult processBlock( final BlockHeader blockHeader, final List transactions, final List ommers, + final Optional> maybeWithdrawals, final PrivateMetadataUpdater privateMetadataUpdater) { final List receipts = new ArrayList<>(); long currentGasUsed = 0; @@ -125,6 +129,7 @@ public BlockProcessingResult processBlock( } return new BlockProcessingResult(Optional.empty(), errorMessage); } + worldStateUpdater.commit(); currentGasUsed += transaction.getGasLimit() - result.getGasRemaining(); @@ -134,6 +139,15 @@ public BlockProcessingResult processBlock( receipts.add(transactionReceipt); } + maybeWithdrawals.ifPresent( + withdrawals -> { + final WorldUpdater worldStateUpdater = worldState.updater(); + for (final Withdrawal withdrawal : withdrawals) { + withdrawalsProcessor.processWithdrawal(withdrawal, worldStateUpdater); + } + worldStateUpdater.commit(); + }); + if (!rewardCoinbase(worldState, blockHeader, ommers, skipZeroBlockRewards)) { // no need to log, rewardCoinbase logs the error. if (worldState instanceof BonsaiPersistedWorldState) { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractProtocolScheduleBuilder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractProtocolScheduleBuilder.java new file mode 100644 index 00000000000..f7e650f9ccb --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractProtocolScheduleBuilder.java @@ -0,0 +1,192 @@ +/* + * Copyright Hyperledger 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.mainnet; + +import org.hyperledger.besu.config.GenesisConfigOptions; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; +import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.ethereum.privacy.PrivateTransactionValidator; +import org.hyperledger.besu.evm.internal.EvmConfiguration; + +import java.math.BigInteger; +import java.util.Optional; +import java.util.OptionalLong; +import java.util.TreeMap; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class AbstractProtocolScheduleBuilder { + + private static final Logger LOG = LoggerFactory.getLogger(AbstractProtocolScheduleBuilder.class); + protected final GenesisConfigOptions config; + protected final ProtocolSpecAdapters protocolSpecAdapters; + protected final PrivacyParameters privacyParameters; + protected final boolean isRevertReasonEnabled; + protected final BadBlockManager badBlockManager = new BadBlockManager(); + protected final boolean quorumCompatibilityMode; + protected final EvmConfiguration evmConfiguration; + + protected AbstractProtocolScheduleBuilder( + final GenesisConfigOptions config, + final ProtocolSpecAdapters protocolSpecAdapters, + final PrivacyParameters privacyParameters, + final boolean isRevertReasonEnabled, + final boolean quorumCompatibilityMode, + final EvmConfiguration evmConfiguration) { + this.config = config; + this.protocolSpecAdapters = protocolSpecAdapters; + this.privacyParameters = privacyParameters; + this.isRevertReasonEnabled = isRevertReasonEnabled; + this.quorumCompatibilityMode = quorumCompatibilityMode; + this.evmConfiguration = evmConfiguration; + } + + protected void initSchedule( + final HeaderBasedProtocolSchedule protocolSchedule, final Optional chainId) { + + final MainnetProtocolSpecFactory specFactory = + new MainnetProtocolSpecFactory( + chainId, + config.getContractSizeLimit(), + config.getEvmStackSize(), + isRevertReasonEnabled, + quorumCompatibilityMode, + config.getEcip1017EraRounds(), + evmConfiguration); + + validateForkOrdering(); + + final TreeMap builders = buildMilestoneMap(specFactory); + + // At this stage, all milestones are flagged with correct modifier, but ProtocolSpecs must be + // inserted _AT_ the modifier block entry. + if (!builders.isEmpty()) { + protocolSpecAdapters.stream() + .forEach( + entry -> { + final long modifierBlock = entry.getKey(); + final BuilderMapEntry parent = + Optional.ofNullable(builders.floorEntry(modifierBlock)) + .orElse(builders.firstEntry()) + .getValue(); + builders.put( + modifierBlock, + new BuilderMapEntry(modifierBlock, parent.getBuilder(), entry.getValue())); + }); + } + + // Create the ProtocolSchedule, such that the Dao/fork milestones can be inserted + builders + .values() + .forEach( + e -> + addProtocolSpec( + protocolSchedule, e.getBlockIdentifier(), e.getBuilder(), e.modifier)); + + postBuildStep(specFactory); + + LOG.info("Protocol schedule created with milestones: {}", protocolSchedule.listMilestones()); + } + + abstract void validateForkOrdering(); + + protected long validateForkOrder( + final String forkName, final OptionalLong thisForkBlock, final long lastForkBlock) { + final long referenceForkBlock = thisForkBlock.orElse(lastForkBlock); + if (lastForkBlock > referenceForkBlock) { + throw new RuntimeException( + String.format( + "Genesis Config Error: '%s' is scheduled for %s %d but it must be on or after %s %d.", + forkName, + getBlockIdentifierName(), + thisForkBlock.getAsLong(), + getBlockIdentifierName(), + lastForkBlock)); + } + return referenceForkBlock; + } + + abstract String getBlockIdentifierName(); + + private TreeMap buildMilestoneMap( + final MainnetProtocolSpecFactory specFactory) { + return createMilestones(specFactory) + .flatMap(Optional::stream) + .collect( + Collectors.toMap( + BuilderMapEntry::getBlockIdentifier, + b -> b, + (existing, replacement) -> replacement, + TreeMap::new)); + } + + abstract Stream> createMilestones( + final MainnetProtocolSpecFactory specFactory); + + protected Optional create( + final OptionalLong blockIdentifier, final ProtocolSpecBuilder builder) { + if (blockIdentifier.isEmpty()) { + return Optional.empty(); + } + final long blockVal = blockIdentifier.getAsLong(); + return Optional.of( + new BuilderMapEntry(blockVal, builder, protocolSpecAdapters.getModifierForBlock(blockVal))); + } + + protected void addProtocolSpec( + final HeaderBasedProtocolSchedule protocolSchedule, + final long blockNumberOrTimestamp, + final ProtocolSpecBuilder definition, + final Function modifier) { + definition + .badBlocksManager(badBlockManager) + .privacyParameters(privacyParameters) + .privateTransactionValidatorBuilder( + () -> new PrivateTransactionValidator(protocolSchedule.getChainId())); + + protocolSchedule.putMilestone( + blockNumberOrTimestamp, modifier.apply(definition).build(protocolSchedule)); + } + + abstract void postBuildStep(final MainnetProtocolSpecFactory specFactory); + + protected static class BuilderMapEntry { + + private final long blockIdentifier; + private final ProtocolSpecBuilder builder; + private final Function modifier; + + public BuilderMapEntry( + final long blockIdentifier, + final ProtocolSpecBuilder builder, + final Function modifier) { + this.blockIdentifier = blockIdentifier; + this.builder = builder; + this.modifier = modifier; + } + + public long getBlockIdentifier() { + return blockIdentifier; + } + + public ProtocolSpecBuilder getBuilder() { + return builder; + } + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BaseFeeBlockBodyValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BaseFeeBlockBodyValidator.java index 9d8683f7081..e3fa7ac65a1 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BaseFeeBlockBodyValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BaseFeeBlockBodyValidator.java @@ -32,7 +32,7 @@ public class BaseFeeBlockBodyValidator extends MainnetBlockBodyValidator { private static final Logger LOG = LoggerFactory.getLogger(BaseFeeBlockBodyValidator.class); - public BaseFeeBlockBodyValidator(final ProtocolSchedule protocolSchedule) { + public BaseFeeBlockBodyValidator(final HeaderBasedProtocolSchedule protocolSchedule) { super(protocolSchedule); } @@ -54,7 +54,7 @@ boolean validateTransactionGasPrice(final Block block) { final List transactions = body.getTransactions(); final TransactionPriceCalculator transactionPriceCalculator = protocolSchedule - .getByBlockNumber(block.getHeader().getNumber()) + .getByBlockHeader(block.getHeader()) .getFeeMarket() .getTransactionPriceCalculator(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BlockProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BlockProcessor.java index e8d1fdd9f2b..ff953f68b2d 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BlockProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BlockProcessor.java @@ -22,9 +22,11 @@ import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; +import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater; import java.util.List; +import java.util.Optional; /** Processes a block. */ public interface BlockProcessor { @@ -79,6 +81,7 @@ default BlockProcessingResult processBlock( block.getHeader(), block.getBody().getTransactions(), block.getBody().getOmmers(), + block.getBody().getWithdrawals(), null); } @@ -112,12 +115,30 @@ default BlockProcessingResult processBlock( * @param privateMetadataUpdater the updater used to update the private metadata for the block * @return the block processing result */ + default BlockProcessingResult processBlock( + final Blockchain blockchain, + final MutableWorldState worldState, + final BlockHeader blockHeader, + final List transactions, + final List ommers, + final PrivateMetadataUpdater privateMetadataUpdater) { + return processBlock( + blockchain, + worldState, + blockHeader, + transactions, + ommers, + Optional.empty(), + privateMetadataUpdater); + } + BlockProcessingResult processBlock( Blockchain blockchain, MutableWorldState worldState, BlockHeader blockHeader, List transactions, List ommers, + Optional> withdrawals, PrivateMetadataUpdater privateMetadataUpdater); /** diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BodyValidation.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BodyValidation.java index 8d8fe3efd84..b777b21934a 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BodyValidation.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BodyValidation.java @@ -20,7 +20,9 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; +import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder; +import org.hyperledger.besu.ethereum.core.encoding.WithdrawalEncoder; import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.ethereum.trie.MerklePatriciaTrie; import org.hyperledger.besu.ethereum.trie.SimpleMerklePatriciaTrie; @@ -63,6 +65,16 @@ public static Hash transactionsRoot(final List transactions) { return Hash.wrap(trie.getRootHash()); } + public static Hash withdrawalsRoot(final List withdrawals) { + final MerklePatriciaTrie trie = trie(); + + IntStream.range(0, withdrawals.size()) + .forEach( + i -> trie.put(indexKey(i), WithdrawalEncoder.encodeOpaqueBytes(withdrawals.get(i)))); + + return Hash.wrap(trie.getRootHash()); + } + /** * Generates the receipt root for a list of receipts * diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/DefaultTimestampSchedule.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/DefaultTimestampSchedule.java new file mode 100644 index 00000000000..15677eb5537 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/DefaultTimestampSchedule.java @@ -0,0 +1,105 @@ +/* + * + * * Copyright Hyperledger 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.mainnet; + +import org.hyperledger.besu.ethereum.core.TransactionFilter; +import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; + +import java.math.BigInteger; +import java.util.Comparator; +import java.util.NavigableSet; +import java.util.Optional; +import java.util.TreeSet; +import java.util.stream.Collectors; + +public class DefaultTimestampSchedule implements TimestampSchedule { + private final NavigableSet protocolSpecs = + new TreeSet<>(Comparator.comparing(TimedProtocolSpec::getTimestamp).reversed()); + private final Optional chainId; + + DefaultTimestampSchedule(final Optional chainId) { + this.chainId = chainId; + } + + @Override + public Optional getByTimestamp(final long timestamp) { + for (final TimedProtocolSpec protocolSpec : protocolSpecs) { + if (protocolSpec.getTimestamp() <= timestamp) { + return Optional.of(protocolSpec.getSpec()); + } + } + return Optional.empty(); + } + + @Override + public Optional getChainId() { + return chainId; + } + + @Override + public void putMilestone(final long timestamp, final ProtocolSpec protocolSpec) { + final TimedProtocolSpec scheduledProtocolSpec = new TimedProtocolSpec(timestamp, protocolSpec); + // Ensure this replaces any existing spec at the same block number. + protocolSpecs.remove(scheduledProtocolSpec); + protocolSpecs.add(scheduledProtocolSpec); + } + + @Override + public String listMilestones() { + return protocolSpecs.stream() + .sorted(Comparator.comparing(TimedProtocolSpec::getTimestamp)) + .map(spec -> spec.getSpec().getName() + ": " + spec.getTimestamp()) + .collect(Collectors.joining(", ", "[", "]")); + } + + @Override + public void setTransactionFilter(final TransactionFilter transactionFilter) { + protocolSpecs.forEach( + spec -> spec.getSpec().getTransactionValidator().setTransactionFilter(transactionFilter)); + } + + @Override + public void setPublicWorldStateArchiveForPrivacyBlockProcessor( + final WorldStateArchive publicWorldStateArchive) { + protocolSpecs.forEach( + spec -> { + final BlockProcessor blockProcessor = spec.getSpec().getBlockProcessor(); + if (PrivacyBlockProcessor.class.isAssignableFrom(blockProcessor.getClass())) + ((PrivacyBlockProcessor) blockProcessor) + .setPublicWorldStateArchive(publicWorldStateArchive); + }); + } + + private static class TimedProtocolSpec { + private final long timestamp; + private final ProtocolSpec spec; + + public TimedProtocolSpec(final long timestamp, final ProtocolSpec spec) { + this.timestamp = timestamp; + this.spec = spec; + } + + public long getTimestamp() { + return timestamp; + } + + public ProtocolSpec getSpec() { + return spec; + } + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/HeaderBasedProtocolSchedule.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/HeaderBasedProtocolSchedule.java new file mode 100644 index 00000000000..697abfc0b7c --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/HeaderBasedProtocolSchedule.java @@ -0,0 +1,34 @@ +/* + * + * * Copyright Hyperledger 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.mainnet; + +import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; + +import java.math.BigInteger; +import java.util.Optional; + +public interface HeaderBasedProtocolSchedule { + + ProtocolSpec getByBlockHeader(final ProcessableBlockHeader blockHeader); + + Optional getChainId(); + + void putMilestone(final long blockOrTimestamp, final ProtocolSpec protocolSpec); + + String listMilestones(); +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockBodyValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockBodyValidator.java index 54947660661..a78d0049764 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockBodyValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockBodyValidator.java @@ -37,9 +37,9 @@ public class MainnetBlockBodyValidator implements BlockBodyValidator { private static final int MAX_OMMERS = 2; private static final int MAX_GENERATION = 6; - protected final ProtocolSchedule protocolSchedule; + protected final HeaderBasedProtocolSchedule protocolSchedule; - public MainnetBlockBodyValidator(final ProtocolSchedule protocolSchedule) { + public MainnetBlockBodyValidator(final HeaderBasedProtocolSchedule protocolSchedule) { this.protocolSchedule = protocolSchedule; } @@ -99,6 +99,30 @@ public boolean validateBodyLight( return false; } + if (!validateWithdrawals(header, body)) { + return false; + } + + return true; + } + + private boolean validateWithdrawals(final BlockHeader header, final BlockBody body) { + final WithdrawalsValidator withdrawalsValidator = + protocolSchedule.getByBlockHeader(header).getWithdrawalsValidator(); + if (!withdrawalsValidator.validateRoot(header.getWithdrawalRoot())) { + LOG.warn("Invalid withdrawals root {}", header.getWithdrawalRoot()); + return false; + } + + final Hash calculatedHash = + body.getWithdrawals().map(BodyValidation::withdrawalsRoot).orElse(Hash.EMPTY); + if (!header.getWithdrawalRoot().equals(calculatedHash)) { + LOG.warn( + "Invalid withdrawals root. Calculated {}, found {}", + calculatedHash, + header.getWithdrawalRoot()); + return false; + } return true; } @@ -223,7 +247,7 @@ private boolean isOmmerValid( final BlockHeader current, final BlockHeader ommer, final HeaderValidationMode ommerValidationMode) { - final ProtocolSpec protocolSpec = protocolSchedule.getByBlockNumber(ommer.getNumber()); + final ProtocolSpec protocolSpec = protocolSchedule.getByBlockHeader(ommer); if (!protocolSpec .getOmmerHeaderValidator() .validateHeader(ommer, context, ommerValidationMode)) { 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 3a5d307098c..cb8ca1a87cc 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,28 @@ public ProtocolSpecBuilder parisDefinition(final GenesisConfigOptions genesisCon evmConfiguration); } + public ProtocolSpecBuilder shanghaiDefinition(final GenesisConfigOptions genesisConfigOptions) { + return MainnetProtocolSpecs.shanghaiDefinition( + chainId, + contractSizeLimit, + evmStackSize, + isRevertReasonEnabled, + genesisConfigOptions, + quorumCompatibilityMode, + evmConfiguration); + } + + public ProtocolSpecBuilder cancunDefinition(final GenesisConfigOptions genesisConfigOptions) { + return MainnetProtocolSpecs.cancunDefinition( + chainId, + contractSizeLimit, + evmStackSize, + isRevertReasonEnabled, + genesisConfigOptions, + quorumCompatibilityMode, + evmConfiguration); + } + public ProtocolSpecBuilder shandongDefinition(final GenesisConfigOptions genesisConfigOptions) { return MainnetProtocolSpecs.shandongDefinition( chainId, 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 495406e35d7..2b2ddcd38a6 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 @@ -25,6 +25,7 @@ import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; +import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.core.feemarket.CoinbaseFeePriceCalculator; import org.hyperledger.besu.ethereum.goquorum.GoQuorumBlockProcessor; import org.hyperledger.besu.ethereum.goquorum.GoQuorumBlockValidator; @@ -51,6 +52,7 @@ import org.hyperledger.besu.evm.gascalculator.LondonGasCalculator; import org.hyperledger.besu.evm.gascalculator.PetersburgGasCalculator; import org.hyperledger.besu.evm.gascalculator.ShandongGasCalculator; +import org.hyperledger.besu.evm.gascalculator.ShanghaiGasCalculator; import org.hyperledger.besu.evm.gascalculator.SpuriousDragonGasCalculator; import org.hyperledger.besu.evm.gascalculator.TangerineWhistleGasCalculator; import org.hyperledger.besu.evm.internal.EvmConfiguration; @@ -80,6 +82,7 @@ public abstract class MainnetProtocolSpecs { public static final int FRONTIER_CONTRACT_SIZE_LIMIT = Integer.MAX_VALUE; public static final int SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT = 24576; + public static final int SHANGHAI_INIT_CODE_SIZE_LIMIT = 2 * SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT; public static final int SHANDONG_CONTRACT_SIZE_LIMIT = 2 * SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT; private static final Address RIPEMD160_PRECOMPILE = @@ -531,7 +534,8 @@ static ProtocolSpecBuilder londonDefinition( TransactionType.FRONTIER, TransactionType.ACCESS_LIST, TransactionType.EIP1559), - quorumCompatibilityMode)) + quorumCompatibilityMode, + Integer.MAX_VALUE)) .transactionProcessorBuilder( (gasCalculator, transactionValidator, @@ -637,6 +641,95 @@ static ProtocolSpecBuilder parisDefinition( .name("ParisFork"); } + static ProtocolSpecBuilder shanghaiDefinition( + final Optional chainId, + final OptionalInt configContractSizeLimit, + final OptionalInt configStackSizeLimit, + final boolean enableRevertReason, + final GenesisConfigOptions genesisConfigOptions, + final boolean quorumCompatibilityMode, + final EvmConfiguration evmConfiguration) { + + final int stackSizeLimit = configStackSizeLimit.orElse(MessageFrame.DEFAULT_MAX_STACK_SIZE); + final BaseFeeMarket baseFeeMarket = getBaseFeeMarket(genesisConfigOptions); + + return parisDefinition( + chainId, + configContractSizeLimit, + configStackSizeLimit, + enableRevertReason, + genesisConfigOptions, + quorumCompatibilityMode, + evmConfiguration) + .gasCalculator(ShanghaiGasCalculator::new) + .evmBuilder( + (gasCalculator, jdCacheConfig) -> + MainnetEVMs.shanghai( + gasCalculator, chainId.orElse(BigInteger.ZERO), evmConfiguration)) + .transactionProcessorBuilder( + (gasCalculator, + transactionValidator, + contractCreationProcessor, + messageCallProcessor) -> + new MainnetTransactionProcessor( + gasCalculator, + transactionValidator, + contractCreationProcessor, + messageCallProcessor, + true, + true, + stackSizeLimit, + baseFeeMarket, + CoinbaseFeePriceCalculator.eip1559())) + // Contract creation rules for EIP-3860 Limit and meter intitcode + .transactionValidatorBuilder( + gasCalculator -> + new MainnetTransactionValidator( + gasCalculator, + baseFeeMarket, + true, + chainId, + Set.of( + TransactionType.FRONTIER, + TransactionType.ACCESS_LIST, + TransactionType.EIP1559), + quorumCompatibilityMode, + SHANGHAI_INIT_CODE_SIZE_LIMIT)) + .withdrawalsProcessorBuilder(WithdrawalsProcessor.AllowedWithdrawalsProcessor::new) + .withdrawalsValidatorBuilder(WithdrawalsValidator.AllowedWithdrawals::new) + .name("Shanghai"); + } + + private static BaseFeeMarket getBaseFeeMarket(final GenesisConfigOptions genesisConfigOptions) { + // TODO SLD bug here? need to default to a later fork e.g. if london isn't specified but + // grayGlacier is. + final long londonForkBlockNumber = + genesisConfigOptions.getLondonBlockNumber().orElse(Long.MAX_VALUE); + return genesisConfigOptions.isZeroBaseFee() + ? FeeMarket.zeroBaseFee(londonForkBlockNumber) + : FeeMarket.london(londonForkBlockNumber, genesisConfigOptions.getBaseFeePerGas()); + } + + static ProtocolSpecBuilder cancunDefinition( + final Optional chainId, + final OptionalInt configContractSizeLimit, + final OptionalInt configStackSizeLimit, + final boolean enableRevertReason, + final GenesisConfigOptions genesisConfigOptions, + final boolean quorumCompatibilityMode, + final EvmConfiguration evmConfiguration) { + + return shanghaiDefinition( + chainId, + configContractSizeLimit, + configStackSizeLimit, + enableRevertReason, + genesisConfigOptions, + quorumCompatibilityMode, + evmConfiguration) + .name("Cancun"); + } + static ProtocolSpecBuilder shandongDefinition( final Optional chainId, final OptionalInt configContractSizeLimit, @@ -767,10 +860,17 @@ public BlockProcessingResult processBlock( final BlockHeader blockHeader, final List transactions, final List ommers, + final Optional> withdrawals, final PrivateMetadataUpdater privateMetadataUpdater) { updateWorldStateForDao(worldState); return wrapped.processBlock( - blockchain, worldState, blockHeader, transactions, ommers, privateMetadataUpdater); + blockchain, + worldState, + blockHeader, + transactions, + ommers, + withdrawals, + privateMetadataUpdater); } private static final Address DAO_REFUND_CONTRACT_ADDRESS = diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java index 2d6783e3bf6..e9781b4b47e 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java @@ -53,6 +53,8 @@ public class MainnetTransactionValidator { private final Set acceptedTransactionTypes; private final boolean goQuorumCompatibilityMode; + private final int maxInitcodeSize; + public MainnetTransactionValidator( final GasCalculator gasCalculator, final boolean checkSignatureMalleability, @@ -78,7 +80,8 @@ public MainnetTransactionValidator( checkSignatureMalleability, chainId, acceptedTransactionTypes, - quorumCompatibilityMode); + quorumCompatibilityMode, + Integer.MAX_VALUE); } public MainnetTransactionValidator( @@ -87,13 +90,15 @@ public MainnetTransactionValidator( final boolean checkSignatureMalleability, final Optional chainId, final Set acceptedTransactionTypes, - final boolean goQuorumCompatibilityMode) { + final boolean goQuorumCompatibilityMode, + final int maxInitcodeSize) { this.gasCalculator = gasCalculator; this.feeMarket = feeMarket; this.disallowSignatureMalleability = checkSignatureMalleability; this.chainId = chainId; this.acceptedTransactionTypes = acceptedTransactionTypes; this.goQuorumCompatibilityMode = goQuorumCompatibilityMode; + this.maxInitcodeSize = maxInitcodeSize; } /** @@ -184,6 +189,14 @@ public ValidationResult validate( intrinsicGasCost, transaction.getGasLimit())); } + if (transaction.isContractCreation() && transaction.getPayload().size() > maxInitcodeSize) { + return ValidationResult.invalid( + TransactionInvalidReason.INITCODE_TOO_LARGE, + String.format( + "Initcode size of %d exceeds maximum size of %s", + transaction.getPayload().size(), maxInitcodeSize)); + } + return ValidationResult.valid(); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MutableProtocolSchedule.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MutableProtocolSchedule.java index 12920e8954c..8145af43ac2 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MutableProtocolSchedule.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MutableProtocolSchedule.java @@ -45,6 +45,7 @@ public Optional getChainId() { return chainId; } + @Override public void putMilestone(final long blockNumber, final ProtocolSpec protocolSpec) { final ScheduledProtocolSpec scheduledProtocolSpec = new ScheduledProtocolSpec(blockNumber, protocolSpec); @@ -70,6 +71,7 @@ public ProtocolSpec getByBlockNumber(final long number) { return null; } + @Override public String listMilestones() { return protocolSpecs.stream() .sorted(Comparator.comparing(ScheduledProtocolSpec::getBlock)) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessor.java index 3859d18d71c..c06d43372a2 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessor.java @@ -25,6 +25,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.privacy.PrivateStateGenesisAllocator; import org.hyperledger.besu.ethereum.privacy.PrivateStateRehydration; import org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver; @@ -51,7 +52,7 @@ public class PrivacyBlockProcessor implements BlockProcessor { private static final Logger LOG = LoggerFactory.getLogger(PrivacyBlockProcessor.class); private final BlockProcessor blockProcessor; - private final ProtocolSchedule protocolSchedule; + private final HeaderBasedProtocolSchedule protocolSchedule; private final Enclave enclave; private final PrivateStateStorage privateStateStorage; private final WorldStateArchive privateWorldStateArchive; @@ -61,7 +62,7 @@ public class PrivacyBlockProcessor implements BlockProcessor { public PrivacyBlockProcessor( final BlockProcessor blockProcessor, - final ProtocolSchedule protocolSchedule, + final HeaderBasedProtocolSchedule protocolSchedule, final Enclave enclave, final PrivateStateStorage privateStateStorage, final WorldStateArchive privateWorldStateArchive, @@ -87,6 +88,7 @@ public BlockProcessingResult processBlock( final BlockHeader blockHeader, final List transactions, final List ommers, + final Optional> withdrawals, final PrivateMetadataUpdater privateMetadataUpdater) { if (privateMetadataUpdater != null) { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PrivacySupportingProtocolSchedule.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PrivacySupportingProtocolSchedule.java new file mode 100644 index 00000000000..d841e4b9eba --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PrivacySupportingProtocolSchedule.java @@ -0,0 +1,29 @@ +/* + * + * * Copyright Hyperledger 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.mainnet; + +import org.hyperledger.besu.ethereum.core.TransactionFilter; +import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; + +public interface PrivacySupportingProtocolSchedule { + + void setTransactionFilter(final TransactionFilter transactionFilter); + + void setPublicWorldStateArchiveForPrivacyBlockProcessor( + final WorldStateArchive publicWorldStateArchive); +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSchedule.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSchedule.java index fc4979f06b5..7cef6b7696f 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSchedule.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSchedule.java @@ -14,23 +14,19 @@ */ package org.hyperledger.besu.ethereum.mainnet; -import org.hyperledger.besu.ethereum.core.TransactionFilter; -import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; -import java.math.BigInteger; -import java.util.Optional; import java.util.stream.Stream; -public interface ProtocolSchedule { +public interface ProtocolSchedule + extends HeaderBasedProtocolSchedule, PrivacySupportingProtocolSchedule { ProtocolSpec getByBlockNumber(long number); Stream streamMilestoneBlocks(); - Optional getChainId(); - - void setTransactionFilter(TransactionFilter transactionFilter); - - void setPublicWorldStateArchiveForPrivacyBlockProcessor( - WorldStateArchive publicWorldStateArchive); + @Override + default ProtocolSpec getByBlockHeader(final ProcessableBlockHeader blockHeader) { + return getByBlockNumber(blockHeader.getNumber()); + } } 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 ebbc3398712..0a881802db4 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 @@ -15,57 +15,19 @@ package org.hyperledger.besu.ethereum.mainnet; import org.hyperledger.besu.config.GenesisConfigOptions; -import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.PrivacyParameters; -import org.hyperledger.besu.ethereum.privacy.PrivateTransactionValidator; import org.hyperledger.besu.evm.internal.EvmConfiguration; import java.math.BigInteger; import java.util.Optional; import java.util.OptionalLong; -import java.util.TreeMap; import java.util.function.Function; -import java.util.stream.Collectors; import java.util.stream.Stream; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +public class ProtocolScheduleBuilder extends AbstractProtocolScheduleBuilder { -public class ProtocolScheduleBuilder { - - private static class BuilderMapEntry { - - private final long block; - private final ProtocolSpecBuilder builder; - private final Function modifier; - - public BuilderMapEntry( - final long block, - final ProtocolSpecBuilder builder, - final Function modifier) { - this.block = block; - this.builder = builder; - this.modifier = modifier; - } - - public long getBlock() { - return block; - } - - public ProtocolSpecBuilder getBuilder() { - return builder; - } - } - - private static final Logger LOG = LoggerFactory.getLogger(ProtocolScheduleBuilder.class); - private final GenesisConfigOptions config; - private final ProtocolSpecAdapters protocolSpecAdapters; private final Optional defaultChainId; - private final PrivacyParameters privacyParameters; - private final boolean isRevertReasonEnabled; - private final BadBlockManager badBlockManager = new BadBlockManager(); - private final boolean quorumCompatibilityMode; - private final EvmConfiguration evmConfiguration; + private MutableProtocolSchedule protocolSchedule; public ProtocolScheduleBuilder( final GenesisConfigOptions config, @@ -85,16 +47,6 @@ public ProtocolScheduleBuilder( evmConfiguration); } - private Optional create( - final OptionalLong block, final ProtocolSpecBuilder builder) { - if (block.isEmpty()) { - return Optional.empty(); - } - final long blockVal = block.getAsLong(); - return Optional.of( - new BuilderMapEntry(blockVal, builder, protocolSpecAdapters.getModifierForBlock(blockVal))); - } - public ProtocolScheduleBuilder( final GenesisConfigOptions config, final ProtocolSpecAdapters protocolSpecAdapters, @@ -120,172 +72,25 @@ private ProtocolScheduleBuilder( final boolean isRevertReasonEnabled, final boolean quorumCompatibilityMode, final EvmConfiguration evmConfiguration) { - this.config = config; + super( + config, + protocolSpecAdapters, + privacyParameters, + isRevertReasonEnabled, + quorumCompatibilityMode, + evmConfiguration); this.defaultChainId = defaultChainId; - this.protocolSpecAdapters = protocolSpecAdapters; - this.privacyParameters = privacyParameters; - this.isRevertReasonEnabled = isRevertReasonEnabled; - this.quorumCompatibilityMode = quorumCompatibilityMode; - this.evmConfiguration = evmConfiguration; } public ProtocolSchedule createProtocolSchedule() { final Optional chainId = config.getChainId().or(() -> defaultChainId); - final MutableProtocolSchedule protocolSchedule = new MutableProtocolSchedule(chainId); - - final MainnetProtocolSpecFactory specFactory = - new MainnetProtocolSpecFactory( - chainId, - config.getContractSizeLimit(), - config.getEvmStackSize(), - isRevertReasonEnabled, - quorumCompatibilityMode, - config.getEcip1017EraRounds(), - evmConfiguration); - - validateForkOrdering(); - - final TreeMap builders = buildMilestoneMap(specFactory); - - // At this stage, all milestones are flagged with correct modifier, but ProtocolSpecs must be - // inserted _AT_ the modifier block entry. - protocolSpecAdapters.stream() - .forEach( - entry -> { - final long modifierBlock = entry.getKey(); - final BuilderMapEntry parent = builders.floorEntry(modifierBlock).getValue(); - builders.put( - modifierBlock, - new BuilderMapEntry(modifierBlock, parent.getBuilder(), entry.getValue())); - }); - - // Create the ProtocolSchedule, such that the Dao/fork milestones can be inserted - builders - .values() - .forEach(e -> addProtocolSpec(protocolSchedule, e.getBlock(), e.getBuilder(), e.modifier)); - - // NOTE: It is assumed that Daofork blocks will not be used for private networks - // as too many risks exist around inserting a protocol-spec between daoBlock and daoBlock+10. - config - .getDaoForkBlock() - .ifPresent( - daoBlockNumber -> { - final ProtocolSpec originalProtocolSpec = - protocolSchedule.getByBlockNumber(daoBlockNumber); - addProtocolSpec( - protocolSchedule, - daoBlockNumber, - specFactory.daoRecoveryInitDefinition(), - protocolSpecAdapters.getModifierForBlock(daoBlockNumber)); - addProtocolSpec( - protocolSchedule, - daoBlockNumber + 1L, - specFactory.daoRecoveryTransitionDefinition(), - protocolSpecAdapters.getModifierForBlock(daoBlockNumber + 1L)); - // Return to the previous protocol spec after the dao fork has completed. - protocolSchedule.putMilestone(daoBlockNumber + 10, originalProtocolSpec); - }); - - // specs for classic network - config - .getClassicForkBlock() - .ifPresent( - classicBlockNumber -> { - final ProtocolSpec originalProtocolSpec = - protocolSchedule.getByBlockNumber(classicBlockNumber); - addProtocolSpec( - protocolSchedule, - OptionalLong.of(classicBlockNumber), - ClassicProtocolSpecs.classicRecoveryInitDefinition( - config.getContractSizeLimit(), - config.getEvmStackSize(), - quorumCompatibilityMode, - evmConfiguration)); - protocolSchedule.putMilestone(classicBlockNumber + 1, originalProtocolSpec); - }); - - LOG.info("Protocol schedule created with milestones: {}", protocolSchedule.listMilestones()); + protocolSchedule = new MutableProtocolSchedule(chainId); + initSchedule(protocolSchedule, chainId); return protocolSchedule; } - private TreeMap buildMilestoneMap( - final MainnetProtocolSpecFactory specFactory) { - return Stream.of( - create(OptionalLong.of(0), specFactory.frontierDefinition()), - create(config.getHomesteadBlockNumber(), specFactory.homesteadDefinition()), - create( - config.getTangerineWhistleBlockNumber(), specFactory.tangerineWhistleDefinition()), - create(config.getSpuriousDragonBlockNumber(), specFactory.spuriousDragonDefinition()), - create(config.getByzantiumBlockNumber(), specFactory.byzantiumDefinition()), - create(config.getConstantinopleBlockNumber(), specFactory.constantinopleDefinition()), - create(config.getPetersburgBlockNumber(), specFactory.petersburgDefinition()), - create(config.getIstanbulBlockNumber(), specFactory.istanbulDefinition()), - create(config.getMuirGlacierBlockNumber(), specFactory.muirGlacierDefinition()), - create(config.getBerlinBlockNumber(), specFactory.berlinDefinition()), - create(config.getLondonBlockNumber(), specFactory.londonDefinition(config)), - create(config.getArrowGlacierBlockNumber(), specFactory.arrowGlacierDefinition(config)), - create(config.getGrayGlacierBlockNumber(), specFactory.grayGlacierDefinition(config)), - create(config.getMergeNetSplitBlockNumber(), specFactory.parisDefinition(config)), - create(config.getShandongBlockNumber(), specFactory.shandongDefinition(config)), - // Classic Milestones - create(config.getEcip1015BlockNumber(), specFactory.tangerineWhistleDefinition()), - create(config.getDieHardBlockNumber(), specFactory.dieHardDefinition()), - create(config.getGothamBlockNumber(), specFactory.gothamDefinition()), - create( - config.getDefuseDifficultyBombBlockNumber(), - specFactory.defuseDifficultyBombDefinition()), - create(config.getAtlantisBlockNumber(), specFactory.atlantisDefinition()), - create(config.getAghartaBlockNumber(), specFactory.aghartaDefinition()), - create(config.getPhoenixBlockNumber(), specFactory.phoenixDefinition()), - create(config.getThanosBlockNumber(), specFactory.thanosDefinition()), - create(config.getMagnetoBlockNumber(), specFactory.magnetoDefinition()), - create(config.getMystiqueBlockNumber(), specFactory.mystiqueDefinition()), - create(config.getEcip1049BlockNumber(), specFactory.ecip1049Definition())) - .filter(Optional::isPresent) - .map(Optional::get) - .collect( - Collectors.toMap( - BuilderMapEntry::getBlock, - b -> b, - (existing, replacement) -> replacement, - TreeMap::new)); - } - - private void addProtocolSpec( - final MutableProtocolSchedule protocolSchedule, - final OptionalLong blockNumber, - final ProtocolSpecBuilder definition) { - blockNumber.ifPresent( - bn -> addProtocolSpec(protocolSchedule, bn, definition, Function.identity())); - } - - private void addProtocolSpec( - final MutableProtocolSchedule protocolSchedule, - final long blockNumber, - final ProtocolSpecBuilder definition, - final Function modifier) { - definition - .badBlocksManager(badBlockManager) - .privacyParameters(privacyParameters) - .privateTransactionValidatorBuilder( - () -> new PrivateTransactionValidator(protocolSchedule.getChainId())); - - protocolSchedule.putMilestone(blockNumber, modifier.apply(definition).build(protocolSchedule)); - } - - private long validateForkOrder( - final String forkName, final OptionalLong thisForkBlock, final long lastForkBlock) { - final long referenceForkBlock = thisForkBlock.orElse(lastForkBlock); - if (lastForkBlock > referenceForkBlock) { - throw new RuntimeException( - String.format( - "Genesis Config Error: '%s' is scheduled for block %d but it must be on or after block %d.", - forkName, thisForkBlock.getAsLong(), lastForkBlock)); - } - return referenceForkBlock; - } - - private void validateForkOrdering() { + @Override + protected void validateForkOrdering() { if (config.getDaoForkBlock().isEmpty()) { validateClassicForkOrdering(); } else { @@ -339,4 +144,88 @@ private void validateClassicForkOrdering() { lastForkBlock = validateForkOrder("Mystique", config.getMystiqueBlockNumber(), lastForkBlock); assert (lastForkBlock >= 0); } + + @Override + protected String getBlockIdentifierName() { + return "block"; + } + + @Override + protected Stream> createMilestones( + final MainnetProtocolSpecFactory specFactory) { + return Stream.of( + create(OptionalLong.of(0), specFactory.frontierDefinition()), + create(config.getHomesteadBlockNumber(), specFactory.homesteadDefinition()), + create(config.getTangerineWhistleBlockNumber(), specFactory.tangerineWhistleDefinition()), + create(config.getSpuriousDragonBlockNumber(), specFactory.spuriousDragonDefinition()), + create(config.getByzantiumBlockNumber(), specFactory.byzantiumDefinition()), + create(config.getConstantinopleBlockNumber(), specFactory.constantinopleDefinition()), + create(config.getPetersburgBlockNumber(), specFactory.petersburgDefinition()), + create(config.getIstanbulBlockNumber(), specFactory.istanbulDefinition()), + create(config.getMuirGlacierBlockNumber(), specFactory.muirGlacierDefinition()), + create(config.getBerlinBlockNumber(), specFactory.berlinDefinition()), + create(config.getLondonBlockNumber(), specFactory.londonDefinition(config)), + create(config.getArrowGlacierBlockNumber(), specFactory.arrowGlacierDefinition(config)), + create(config.getGrayGlacierBlockNumber(), specFactory.grayGlacierDefinition(config)), + create(config.getMergeNetSplitBlockNumber(), specFactory.parisDefinition(config)), + create(config.getShandongBlockNumber(), specFactory.shandongDefinition(config)), + // Classic Milestones + create(config.getEcip1015BlockNumber(), specFactory.tangerineWhistleDefinition()), + create(config.getDieHardBlockNumber(), specFactory.dieHardDefinition()), + create(config.getGothamBlockNumber(), specFactory.gothamDefinition()), + create( + config.getDefuseDifficultyBombBlockNumber(), + specFactory.defuseDifficultyBombDefinition()), + create(config.getAtlantisBlockNumber(), specFactory.atlantisDefinition()), + create(config.getAghartaBlockNumber(), specFactory.aghartaDefinition()), + create(config.getPhoenixBlockNumber(), specFactory.phoenixDefinition()), + create(config.getThanosBlockNumber(), specFactory.thanosDefinition()), + create(config.getMagnetoBlockNumber(), specFactory.magnetoDefinition()), + create(config.getMystiqueBlockNumber(), specFactory.mystiqueDefinition()), + create(config.getEcip1049BlockNumber(), specFactory.ecip1049Definition())); + } + + @Override + protected void postBuildStep(final MainnetProtocolSpecFactory specFactory) { + // NOTE: It is assumed that Daofork blocks will not be used for private networks + // as too many risks exist around inserting a protocol-spec between daoBlock and daoBlock+10. + config + .getDaoForkBlock() + .ifPresent( + daoBlockNumber -> { + final ProtocolSpec originalProtocolSpec = + protocolSchedule.getByBlockNumber(daoBlockNumber); + addProtocolSpec( + protocolSchedule, + daoBlockNumber, + specFactory.daoRecoveryInitDefinition(), + protocolSpecAdapters.getModifierForBlock(daoBlockNumber)); + addProtocolSpec( + protocolSchedule, + daoBlockNumber + 1L, + specFactory.daoRecoveryTransitionDefinition(), + protocolSpecAdapters.getModifierForBlock(daoBlockNumber + 1L)); + // Return to the previous protocol spec after the dao fork has completed. + protocolSchedule.putMilestone(daoBlockNumber + 10, originalProtocolSpec); + }); + + // specs for classic network + config + .getClassicForkBlock() + .ifPresent( + classicBlockNumber -> { + final ProtocolSpec originalProtocolSpec = + protocolSchedule.getByBlockNumber(classicBlockNumber); + addProtocolSpec( + protocolSchedule, + classicBlockNumber, + ClassicProtocolSpecs.classicRecoveryInitDefinition( + config.getContractSizeLimit(), + config.getEvmStackSize(), + quorumCompatibilityMode, + evmConfiguration), + Function.identity()); + protocolSchedule.putMilestone(classicBlockNumber + 1, originalProtocolSpec); + }); + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpec.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpec.java index 60125baba56..03e89692357 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpec.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpec.java @@ -75,6 +75,8 @@ public class ProtocolSpec { private final BadBlockManager badBlockManager; private final Optional powHasher; + private final WithdrawalsProcessor withdrawalsProcessor; + private final WithdrawalsValidator withdrawalsValidator; /** * Creates a new protocol specification instance. @@ -102,6 +104,8 @@ public class ProtocolSpec { * @param feeMarket an {@link Optional} wrapping {@link FeeMarket} class if appropriate. * @param badBlockManager the cache to use to keep invalid blocks * @param powHasher the proof-of-work hasher + * @param withdrawalsProcessor the withdrawals processor to use + * @param withdrawalsValidator the withdrawals validator to use */ public ProtocolSpec( final String name, @@ -126,7 +130,9 @@ public ProtocolSpec( final GasLimitCalculator gasLimitCalculator, final FeeMarket feeMarket, final BadBlockManager badBlockManager, - final Optional powHasher) { + final Optional powHasher, + final WithdrawalsProcessor withdrawalsProcessor, + final WithdrawalsValidator withdrawalsValidator) { this.name = name; this.evm = evm; this.transactionValidator = transactionValidator; @@ -150,6 +156,8 @@ public ProtocolSpec( this.feeMarket = feeMarket; this.badBlockManager = badBlockManager; this.powHasher = powHasher; + this.withdrawalsProcessor = withdrawalsProcessor; + this.withdrawalsValidator = withdrawalsValidator; } /** @@ -349,4 +357,12 @@ public BadBlockManager getBadBlocksManager() { public Optional getPoWHasher() { return powHasher; } + + public WithdrawalsProcessor getWithdrawalsProcessor() { + return withdrawalsProcessor; + } + + public WithdrawalsValidator getWithdrawalsValidator() { + return withdrawalsValidator; + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecAdapters.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecAdapters.java index 03bb2a46356..e328357a1ec 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecAdapters.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecAdapters.java @@ -31,16 +31,17 @@ public ProtocolSpecAdapters( } public static ProtocolSpecAdapters create( - final long block, final Function modifier) { + final long blockNumberOrTimestamp, + final Function modifier) { final Map> entries = new HashMap<>(); - entries.put(block, modifier); + entries.put(blockNumberOrTimestamp, modifier); return new ProtocolSpecAdapters(entries); } public Function getModifierForBlock( - final long blockNumber) { + final long blockNumberOrTimestamp) { final NavigableSet epochs = new TreeSet<>(modifiers.keySet()); - final Long modifier = epochs.floor(blockNumber); + final Long modifier = epochs.floor(blockNumberOrTimestamp); if (modifier == null) { return Function.identity(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java index 134d9285e9f..1050058491a 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java @@ -57,7 +57,7 @@ public class ProtocolSpecBuilder { private Function transactionValidatorBuilder; private Function blockHeaderValidatorBuilder; private Function ommerHeaderValidatorBuilder; - private Function blockBodyValidatorBuilder; + private Function blockBodyValidatorBuilder; private BiFunction contractCreationProcessorBuilder; private Function precompileContractRegistryBuilder; @@ -72,6 +72,10 @@ public class ProtocolSpecBuilder { private PrivacyParameters privacyParameters; private PrivateTransactionProcessorBuilder privateTransactionProcessorBuilder; private PrivateTransactionValidatorBuilder privateTransactionValidatorBuilder; + + private WithdrawalsProcessorBuilder withdrawalsProcessorBuilder; + private WithdrawalsValidatorBuilder withdrawalsValidatorBuilder; + private FeeMarket feeMarket = FeeMarket.legacy(); private BadBlockManager badBlockManager; private PoWHasher powHasher = PoWHasher.ETHASH_LIGHT; @@ -137,7 +141,7 @@ public ProtocolSpecBuilder ommerHeaderValidatorBuilder( } public ProtocolSpecBuilder blockBodyValidatorBuilder( - final Function blockBodyValidatorBuilder) { + final Function blockBodyValidatorBuilder) { this.blockBodyValidatorBuilder = blockBodyValidatorBuilder; return this; } @@ -243,7 +247,19 @@ public ProtocolSpecBuilder evmConfiguration(final EvmConfiguration evmConfigurat return this; } - public ProtocolSpec build(final ProtocolSchedule protocolSchedule) { + public ProtocolSpecBuilder withdrawalsProcessorBuilder( + final WithdrawalsProcessorBuilder withdrawalsProcessorBuilder) { + this.withdrawalsProcessorBuilder = withdrawalsProcessorBuilder; + return this; + } + + public ProtocolSpecBuilder withdrawalsValidatorBuilder( + final WithdrawalsValidatorBuilder withdrawalsValidatorBuilder) { + this.withdrawalsValidatorBuilder = withdrawalsValidatorBuilder; + return this; + } + + public ProtocolSpec build(final HeaderBasedProtocolSchedule protocolSchedule) { checkNotNull(gasCalculatorBuilder, "Missing gasCalculator"); checkNotNull(gasLimitCalculator, "Missing gasLimitCalculator"); checkNotNull(evmBuilder, "Missing operation registry"); @@ -288,54 +304,23 @@ public ProtocolSpec build(final ProtocolSchedule protocolSchedule) { gasCalculator, transactionValidator, contractCreationProcessor, messageCallProcessor); final BlockHeaderValidator blockHeaderValidator = - blockHeaderValidatorBuilder - .apply(feeMarket) - .difficultyCalculator(difficultyCalculator) - .build(); + createBlockHeaderValidator(blockHeaderValidatorBuilder); final BlockHeaderValidator ommerHeaderValidator = - ommerHeaderValidatorBuilder - .apply(feeMarket) - .difficultyCalculator(difficultyCalculator) - .build(); + createBlockHeaderValidator(ommerHeaderValidatorBuilder); + final BlockBodyValidator blockBodyValidator = blockBodyValidatorBuilder.apply(protocolSchedule); - BlockProcessor blockProcessor = - blockProcessorBuilder.apply( - transactionProcessor, - transactionReceiptFactory, - blockReward, - miningBeneficiaryCalculator, - skipZeroBlockRewards, - privacyParameters.getGoQuorumPrivacyParameters()); + BlockProcessor blockProcessor = createBlockProcessor(transactionProcessor); // Set private Tx Processor - PrivateTransactionProcessor privateTransactionProcessor = null; - if (privacyParameters.isEnabled()) { - final PrivateTransactionValidator privateTransactionValidator = - privateTransactionValidatorBuilder.apply(); - privateTransactionProcessor = - privateTransactionProcessorBuilder.apply( - transactionValidator, - contractCreationProcessor, - messageCallProcessor, - privateTransactionValidator); - - if (privacyParameters.isPrivacyPluginEnabled()) { - final PrivacyPluginPrecompiledContract privacyPluginPrecompiledContract = - (PrivacyPluginPrecompiledContract) precompileContractRegistry.get(PLUGIN_PRIVACY); - privacyPluginPrecompiledContract.setPrivateTransactionProcessor( - privateTransactionProcessor); - } else if (privacyParameters.isFlexiblePrivacyGroupsEnabled()) { - final FlexiblePrivacyPrecompiledContract flexiblePrivacyPrecompiledContract = - (FlexiblePrivacyPrecompiledContract) precompileContractRegistry.get(FLEXIBLE_PRIVACY); - flexiblePrivacyPrecompiledContract.setPrivateTransactionProcessor( - privateTransactionProcessor); - } else { - final PrivacyPrecompiledContract privacyPrecompiledContract = - (PrivacyPrecompiledContract) precompileContractRegistry.get(DEFAULT_PRIVACY); - privacyPrecompiledContract.setPrivateTransactionProcessor(privateTransactionProcessor); - } + PrivateTransactionProcessor privateTransactionProcessor = + createPrivateTransactionProcessor( + transactionValidator, + contractCreationProcessor, + messageCallProcessor, + precompileContractRegistry); + if (privacyParameters.isEnabled()) { blockProcessor = new PrivacyBlockProcessor( blockProcessor, @@ -347,6 +332,16 @@ public ProtocolSpec build(final ProtocolSchedule protocolSchedule) { privacyParameters.getPrivateStateGenesisAllocator()); } + WithdrawalsProcessor withdrawalsProcessor = + new WithdrawalsProcessor.ProhibitedWithdrawalsProcessor(); + if (withdrawalsProcessorBuilder != null) { + withdrawalsProcessor = withdrawalsProcessorBuilder.apply(); + } + WithdrawalsValidator withdrawalsValidator = new WithdrawalsValidator.ProhibitedWithdrawals(); + if (withdrawalsValidatorBuilder != null) { + withdrawalsValidator = withdrawalsValidatorBuilder.apply(); + } + final BlockValidator blockValidator = blockValidatorBuilder.apply( blockHeaderValidator, @@ -378,7 +373,63 @@ public ProtocolSpec build(final ProtocolSchedule protocolSchedule) { gasLimitCalculator, feeMarket, badBlockManager, - Optional.ofNullable(powHasher)); + Optional.ofNullable(powHasher), + withdrawalsProcessor, + withdrawalsValidator); + } + + private PrivateTransactionProcessor createPrivateTransactionProcessor( + final MainnetTransactionValidator transactionValidator, + final AbstractMessageProcessor contractCreationProcessor, + final AbstractMessageProcessor messageCallProcessor, + final PrecompileContractRegistry precompileContractRegistry) { + PrivateTransactionProcessor privateTransactionProcessor = null; + if (privacyParameters.isEnabled()) { + final PrivateTransactionValidator privateTransactionValidator = + privateTransactionValidatorBuilder.apply(); + privateTransactionProcessor = + privateTransactionProcessorBuilder.apply( + transactionValidator, + contractCreationProcessor, + messageCallProcessor, + privateTransactionValidator); + + if (privacyParameters.isPrivacyPluginEnabled()) { + final PrivacyPluginPrecompiledContract privacyPluginPrecompiledContract = + (PrivacyPluginPrecompiledContract) precompileContractRegistry.get(PLUGIN_PRIVACY); + privacyPluginPrecompiledContract.setPrivateTransactionProcessor( + privateTransactionProcessor); + } else if (privacyParameters.isFlexiblePrivacyGroupsEnabled()) { + final FlexiblePrivacyPrecompiledContract flexiblePrivacyPrecompiledContract = + (FlexiblePrivacyPrecompiledContract) precompileContractRegistry.get(FLEXIBLE_PRIVACY); + flexiblePrivacyPrecompiledContract.setPrivateTransactionProcessor( + privateTransactionProcessor); + } else { + final PrivacyPrecompiledContract privacyPrecompiledContract = + (PrivacyPrecompiledContract) precompileContractRegistry.get(DEFAULT_PRIVACY); + privacyPrecompiledContract.setPrivateTransactionProcessor(privateTransactionProcessor); + } + } + return privateTransactionProcessor; + } + + private BlockProcessor createBlockProcessor( + final MainnetTransactionProcessor transactionProcessor) { + return blockProcessorBuilder.apply( + transactionProcessor, + transactionReceiptFactory, + blockReward, + miningBeneficiaryCalculator, + skipZeroBlockRewards, + privacyParameters.getGoQuorumPrivacyParameters()); + } + + private BlockHeaderValidator createBlockHeaderValidator( + final Function blockHeaderValidatorBuilder) { + return blockHeaderValidatorBuilder + .apply(feeMarket) + .difficultyCalculator(difficultyCalculator) + .build(); } public interface TransactionProcessorBuilder { @@ -420,6 +471,14 @@ BlockValidator apply( Optional goQuorumPrivacyParameters); } + public interface WithdrawalsProcessorBuilder { + WithdrawalsProcessor apply(); + } + + public interface WithdrawalsValidatorBuilder { + WithdrawalsValidator apply(); + } + public interface BlockImporterBuilder { BlockImporter apply(BlockValidator blockValidator); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TimestampSchedule.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TimestampSchedule.java new file mode 100644 index 00000000000..3e669dadf9e --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TimestampSchedule.java @@ -0,0 +1,32 @@ +/* + * + * * Copyright Hyperledger 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.mainnet; + +import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; + +import java.util.Optional; + +public interface TimestampSchedule + extends HeaderBasedProtocolSchedule, PrivacySupportingProtocolSchedule { + Optional getByTimestamp(final long timestamp); + + @Override + default ProtocolSpec getByBlockHeader(final ProcessableBlockHeader blockHeader) { + return getByTimestamp(blockHeader.getTimestamp()).orElse(null); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TimestampScheduleBuilder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TimestampScheduleBuilder.java new file mode 100644 index 00000000000..851c97a8a13 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TimestampScheduleBuilder.java @@ -0,0 +1,81 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet; + +import org.hyperledger.besu.config.GenesisConfigOptions; +import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.evm.internal.EvmConfiguration; + +import java.math.BigInteger; +import java.util.Optional; +import java.util.stream.Stream; + +public class TimestampScheduleBuilder extends AbstractProtocolScheduleBuilder { + + private final Optional defaultChainId; + + public TimestampScheduleBuilder( + final GenesisConfigOptions config, + final BigInteger defaultChainId, + final ProtocolSpecAdapters protocolSpecAdapters, + final PrivacyParameters privacyParameters, + final boolean isRevertReasonEnabled, + final boolean quorumCompatibilityMode, + final EvmConfiguration evmConfiguration) { + super( + config, + protocolSpecAdapters, + privacyParameters, + isRevertReasonEnabled, + quorumCompatibilityMode, + evmConfiguration); + this.defaultChainId = Optional.of(defaultChainId); + } + + public TimestampSchedule createTimestampSchedule() { + final Optional chainId = config.getChainId().or(() -> defaultChainId); + TimestampSchedule timestampSchedule = new DefaultTimestampSchedule(chainId); + initSchedule(timestampSchedule, chainId); + return timestampSchedule; + } + + @Override + protected void validateForkOrdering() { + long lastForkTimestamp = 0; + lastForkTimestamp = validateForkOrder("Shanghai", config.getShanghaiTime(), lastForkTimestamp); + lastForkTimestamp = validateForkOrder("Cancun", config.getCancunTime(), lastForkTimestamp); + assert (lastForkTimestamp >= 0); + } + + @Override + protected String getBlockIdentifierName() { + return "timestamp"; + } + + @Override + protected Stream> createMilestones( + final MainnetProtocolSpecFactory specFactory) { + return Stream.of( + // generally this TimestampSchedule will not have an entry for 0 instead it is relying + // on defaulting to a MergeProtocolSchedule in + // TransitionProtocolSchedule.getByBlockHeader if the given timestamp is before the + // first entry in TimestampSchedule + create(config.getShanghaiTime(), specFactory.shanghaiDefinition(config)), + create(config.getCancunTime(), specFactory.cancunDefinition(config))); + } + + @Override + protected void postBuildStep(final MainnetProtocolSpecFactory specFactory) {} +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalsProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalsProcessor.java new file mode 100644 index 00000000000..3d827a4f246 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalsProcessor.java @@ -0,0 +1,58 @@ +/* + * + * * Copyright Hyperledger 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.mainnet; + +import org.hyperledger.besu.ethereum.core.Withdrawal; +import org.hyperledger.besu.evm.account.EvmAccount; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public interface WithdrawalsProcessor { + void processWithdrawal(final Withdrawal withdrawal, final WorldUpdater worldUpdater); + + class ProhibitedWithdrawalsProcessor implements WithdrawalsProcessor { + @Override + public void processWithdrawal(final Withdrawal withdrawal, final WorldUpdater worldUpdater) { + throw new UnsupportedOperationException("Withdrawals are not allowed on this chain"); + } + } + + class AllowedWithdrawalsProcessor implements WithdrawalsProcessor { + + private static final Logger LOG = LoggerFactory.getLogger(AllowedWithdrawalsProcessor.class); + + @Override + public void processWithdrawal(final Withdrawal withdrawal, final WorldUpdater worldUpdater) { + try { + final EvmAccount account = worldUpdater.getOrCreate(withdrawal.getAddress()); + account + .getMutable() + .setBalance(account.getBalance().add(withdrawal.getAmount().getAsWei())); + worldUpdater.commit(); + } catch (Exception e) { + final String message = + String.format( + "failed to process withdrawal for address: %s of %s wei", + withdrawal.getAddress(), withdrawal.getAmount()); + LOG.error(message, e); + } + } + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalsValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalsValidator.java new file mode 100644 index 00000000000..c286a7bbb7e --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalsValidator.java @@ -0,0 +1,81 @@ +/* + * + * * Copyright Hyperledger 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.mainnet; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.core.Withdrawal; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public interface WithdrawalsValidator { + + boolean validateRoot(Hash withdrawalRoot); + + boolean validateWithdrawals(List withdrawals); + + class ProhibitedWithdrawals implements WithdrawalsValidator { + + private static final Logger LOG = LoggerFactory.getLogger(ProhibitedWithdrawals.class); + + @Override + public boolean validateRoot(final Hash withdrawalRoot) { + final boolean isValid = withdrawalRoot.equals(Hash.EMPTY); + if (!isValid) { + LOG.warn("Withdrawals root: {} should equal empty hash: {}", withdrawalRoot, Hash.EMPTY); + } + return isValid; + } + + @Override + public boolean validateWithdrawals(final List withdrawals) { + final boolean isValid = withdrawals == null; + if (!isValid) { + LOG.warn( + "withdrawals should be null when Withdrawals are prohibited but were: {}", withdrawals); + } + return isValid; + } + } + + class AllowedWithdrawals implements WithdrawalsValidator { + + private static final Logger LOG = LoggerFactory.getLogger(AllowedWithdrawals.class); + + @Override + public boolean validateRoot(final Hash withdrawalRoot) { + final boolean isValid = !withdrawalRoot.equals(Hash.EMPTY); + if (!isValid) { + LOG.warn( + "Withdrawals root: {} should not equal empty hash: {}", withdrawalRoot, Hash.EMPTY); + } + return isValid; + } + + @Override + public boolean validateWithdrawals(final List withdrawals) { + final boolean isValid = withdrawals != null; + if (!isValid) { + LOG.warn("withdrawals should not be null when Withdrawals are activated"); + } + return isValid; + } + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateStateRehydration.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateStateRehydration.java index c7093c671b1..37d83a7d41e 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateStateRehydration.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateStateRehydration.java @@ -21,7 +21,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.Transaction; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.HeaderBasedProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap; import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; @@ -43,7 +43,7 @@ public class PrivateStateRehydration { private final PrivateStateStorage privateStateStorage; private final Blockchain blockchain; - private final ProtocolSchedule protocolSchedule; + private final HeaderBasedProtocolSchedule protocolSchedule; private final WorldStateArchive publicWorldStateArchive; private final WorldStateArchive privateWorldStateArchive; private final PrivateStateRootResolver privateStateRootResolver; @@ -52,7 +52,7 @@ public class PrivateStateRehydration { public PrivateStateRehydration( final PrivateStateStorage privateStateStorage, final Blockchain blockchain, - final ProtocolSchedule protocolSchedule, + final HeaderBasedProtocolSchedule protocolSchedule, final WorldStateArchive publicWorldStateArchive, final WorldStateArchive privateWorldStateArchive, final PrivateStateRootResolver privateStateRootResolver, @@ -140,7 +140,7 @@ public void rehydrate( .map(Transaction::getHash) .collect(Collectors.toList())); - final ProtocolSpec protocolSpec = protocolSchedule.getByBlockNumber(blockHeader.getNumber()); + final ProtocolSpec protocolSpec = protocolSchedule.getByBlockHeader(blockHeader); final PrivateGroupRehydrationBlockProcessor privateGroupRehydrationBlockProcessor = new PrivateGroupRehydrationBlockProcessor( protocolSpec.getTransactionProcessor(), diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java index 25bd6f0ed6a..0d3c8551665 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java @@ -37,6 +37,7 @@ public enum TransactionInvalidReason { TRANSACTION_ALREADY_KNOWN, TRANSACTION_REPLACEMENT_UNDERPRICED, MAX_PRIORITY_FEE_PER_GAS_EXCEEDS_MAX_FEE_PER_GAS, + INITCODE_TOO_LARGE, // Private Transaction Invalid Reasons PRIVATE_TRANSACTION_FAILED, PRIVATE_NONCE_TOO_LOW, diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/util/RawBlockIterator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/util/RawBlockIterator.java index ef980ef5ff4..a4685837c36 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/util/RawBlockIterator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/util/RawBlockIterator.java @@ -18,6 +18,7 @@ import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.ethereum.rlp.RLPInput; @@ -29,6 +30,7 @@ import java.nio.file.Path; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.Optional; import java.util.function.Function; import org.apache.tuweni.bytes.Bytes; @@ -101,7 +103,12 @@ private void nextBlock() throws IOException { rlp.enterList(); final BlockHeader header = headerReader.apply(rlp); final BlockBody body = - new BlockBody(rlp.readList(Transaction::readFrom), rlp.readList(headerReader)); + new BlockBody( + rlp.readList(Transaction::readFrom), + rlp.readList(headerReader), + rlp.isEndOfCurrentList() + ? Optional.empty() + : Optional.of(rlp.readList(Withdrawal::readFrom))); next = new Block(header, body); readBuffer.position(length); readBuffer.compact(); diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java index 8cce3918037..e63892b9889 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java @@ -290,6 +290,7 @@ public BlockHeader header(final long number, final BlockBody body, final BlockOp .coinbase(options.getCoinbase(address())) .stateRoot(options.getStateRoot(hash())) .transactionsRoot(BodyValidation.transactionsRoot(body.getTransactions())) + .withdrawalsRoot(options.getWithdrawalsRoot()) .receiptsRoot(options.getReceiptsRoot(hash())) .logsBloom(options.getLogsBloom(logsBloom())) .difficulty(options.getDifficulty(Difficulty.of(uint256(4)))) @@ -327,7 +328,7 @@ public BlockBody body(final BlockOptions options) { defaultTxs.add(transaction(options.getTransactionTypes())); } - return new BlockBody(options.getTransactions(defaultTxs), ommers); + return new BlockBody(options.getTransactions(defaultTxs), ommers, options.getWithdrawals()); } private BlockHeader ommer() { @@ -630,6 +631,8 @@ public static class BlockOptions { private TransactionType[] transactionTypes = TransactionType.values(); private Optional
coinbase = Optional.empty(); private Optional> maybeBaseFee = Optional.empty(); + private Optional> maybeWithdrawals = Optional.empty(); + private Hash withdrawalsRoot = Hash.EMPTY; public static BlockOptions create() { return new BlockOptions(); @@ -791,5 +794,23 @@ public BlockOptions setBaseFee(final Optional baseFee) { this.maybeBaseFee = Optional.of(baseFee); return this; } + + public Optional> getWithdrawals() { + return this.maybeWithdrawals; + } + + public BlockOptions setWithdrawals(final Optional> withdrawals) { + this.maybeWithdrawals = withdrawals; + return this; + } + + public Hash getWithdrawalsRoot() { + return this.withdrawalsRoot; + } + + public BlockOptions setWithdrawalsRoot(final Hash root) { + this.withdrawalsRoot = root; + return this; + } } } diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/NonBesuBlockHeader.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/NonBesuBlockHeader.java index 86a0fdd1a94..8e1442560b6 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/NonBesuBlockHeader.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/NonBesuBlockHeader.java @@ -110,4 +110,9 @@ public long getNonce() { public Hash getBlockHash() { return blockHash; } + + @Override + public Hash getWithdrawalRoot() { + return null; + } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/LogRollingTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/LogRollingTests.java index 7c4e1b41014..b5836019873 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/LogRollingTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/LogRollingTests.java @@ -86,6 +86,7 @@ public class LogRollingTests { Wei.ZERO, Hash.ZERO, 0, + Hash.EMPTY, new MainnetBlockHeaderFunctions()); private static final BlockHeader headerTwo = new BlockHeader( @@ -105,6 +106,7 @@ public class LogRollingTests { Wei.ZERO, Hash.ZERO, 0, + Hash.EMPTY, new MainnetBlockHeaderFunctions()); @Before diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalDecoderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalDecoderTest.java new file mode 100644 index 00000000000..a23bf24cff8 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalDecoderTest.java @@ -0,0 +1,62 @@ +/* + * Copyright Hyperledger 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.encoding; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.GWei; +import org.hyperledger.besu.ethereum.core.Withdrawal; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.units.bigints.UInt64; +import org.junit.jupiter.api.Test; + +class WithdrawalDecoderTest { + + @Test + void shouldDecodeWithdrawalForZeroCase() { + Withdrawal withdrawal = + WithdrawalDecoder.decodeOpaqueBytes( + Bytes.fromHexString(WithdrawalEncoderTest.WITHDRAWAL_ZERO_CASE)); + assertThat(withdrawal.getIndex()).isEqualTo(UInt64.ZERO); + assertThat(withdrawal.getValidatorIndex()).isEqualTo(UInt64.ZERO); + assertThat(withdrawal.getAddress()).isEqualTo(Address.ZERO); + assertThat(withdrawal.getAmount()).isEqualTo(GWei.ZERO); + } + + @Test + void shouldDecodeWithdrawalForMaxValue() { + Withdrawal withdrawal = + WithdrawalDecoder.decodeOpaqueBytes( + Bytes.fromHexString(WithdrawalEncoderTest.WITHDRAWAL_MAX_VALUE)); + assertThat(withdrawal.getIndex()).isEqualTo(UInt64.MAX_VALUE); + assertThat(withdrawal.getValidatorIndex()).isEqualTo(UInt64.MAX_VALUE); + assertThat(withdrawal.getAddress()).isEqualTo(WithdrawalEncoderTest.MAX_ADDRESS); + assertThat(withdrawal.getAmount()).isEqualTo(GWei.MAX_GWEI); + } + + @Test + void shouldDecodeWithdrawal() { + final Withdrawal expectedWithdrawal = + new Withdrawal( + UInt64.valueOf(3), UInt64.valueOf(1), Address.fromHexString("0xdeadbeef"), GWei.of(5)); + final Withdrawal withdrawal = + WithdrawalDecoder.decodeOpaqueBytes( + Bytes.fromHexString("0xd803019400000000000000000000000000000000deadbeef05")); + + assertThat(withdrawal).isEqualTo(expectedWithdrawal); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalEncoderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalEncoderTest.java new file mode 100644 index 00000000000..3c80cd78712 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalEncoderTest.java @@ -0,0 +1,62 @@ +/* + * Copyright Hyperledger 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.encoding; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.GWei; +import org.hyperledger.besu.ethereum.core.Withdrawal; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.units.bigints.UInt64; +import org.junit.jupiter.api.Test; + +class WithdrawalEncoderTest { + public static final String WITHDRAWAL_ZERO_CASE = + "0xd8808094000000000000000000000000000000000000000080"; + public static final String WITHDRAWAL_MAX_VALUE = + "0xf088ffffffffffffffff88ffffffffffffffff94ffffffffffffffffffffffffffffffffffffffff88ffffffffffffffff"; + public static final Address MAX_ADDRESS = + Address.fromHexString(Bytes.repeat((byte) 0xff, 20).toHexString()); + + @Test + void shouldEncodeWithdrawalForZeroCase() { + final Withdrawal withdrawal = new Withdrawal(UInt64.ZERO, UInt64.ZERO, Address.ZERO, GWei.ZERO); + final Bytes bytes = WithdrawalEncoder.encodeOpaqueBytes(withdrawal); + assertThat(bytes.toHexString()).isEqualTo(WITHDRAWAL_ZERO_CASE); + } + + @Test + void shouldEncodeWithdrawalForMaxValues() { + final Withdrawal withdrawal = + new Withdrawal(UInt64.MAX_VALUE, UInt64.MAX_VALUE, MAX_ADDRESS, GWei.MAX_GWEI); + final Bytes bytes = WithdrawalEncoder.encodeOpaqueBytes(withdrawal); + assertThat(bytes.toHexString()).isEqualTo(WITHDRAWAL_MAX_VALUE); + } + + @Test + void shouldEncode() { + final UInt64 index = UInt64.valueOf(3); + final UInt64 validatorIndex = UInt64.valueOf(1); + final Address address = Address.fromHexString("0xdeadbeef"); + final GWei amount = GWei.of(5); + final Withdrawal withdrawal = new Withdrawal(index, validatorIndex, address, amount); + final Bytes encoded = WithdrawalEncoder.encodeOpaqueBytes(withdrawal); + + assertThat(encoded) + .isEqualTo(Bytes.fromHexString("0xd803019400000000000000000000000000000000deadbeef05")); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/BaseFeeBlockBodyValidatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/BaseFeeBlockBodyValidatorTest.java index cb1623fcf4c..803df1352e1 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/BaseFeeBlockBodyValidatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/BaseFeeBlockBodyValidatorTest.java @@ -15,7 +15,7 @@ package org.hyperledger.besu.ethereum.mainnet; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; import org.hyperledger.besu.crypto.KeyPair; @@ -54,7 +54,7 @@ public class BaseFeeBlockBodyValidatorTest { public void setup() { when(protocolSpec.getFeeMarket()).thenReturn(FeeMarket.london(0L)); - when(protocolSchedule.getByBlockNumber(anyLong())).thenReturn(protocolSpec); + when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); when(block.getHeader()).thenReturn(blockHeader); @@ -76,7 +76,8 @@ public void BlockBodyValidatorSucceed() { .createTransaction(keyPair), // frontier transaction new TransactionTestFixture().gasPrice(Wei.of(10L)).createTransaction(keyPair)), - Collections.emptyList())); + Collections.emptyList(), + Optional.empty())); assertThat(blockBodyValidator.validateTransactionGasPrice(block)).isTrue(); } @@ -90,7 +91,8 @@ public void BlockBodyValidatorFail_GasPrice() { List.of( // underpriced frontier transaction new TransactionTestFixture().gasPrice(Wei.of(9L)).createTransaction(keyPair)), - Collections.emptyList())); + Collections.emptyList(), + Optional.empty())); assertThat(blockBodyValidator.validateTransactionGasPrice(block)).isFalse(); } @@ -108,7 +110,8 @@ public void BlockBodyValidatorFail_MaxFeePerGas() { .maxPriorityFeePerGas(Optional.of(Wei.of(10L))) .type(TransactionType.EIP1559) .createTransaction(keyPair)), - Collections.emptyList())); + Collections.emptyList(), + Optional.empty())); assertThat(blockBodyValidator.validateTransactionGasPrice(block)).isFalse(); } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/BodyValidationTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/BodyValidationTest.java index 8e33b2b95bf..f28b97101e6 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/BodyValidationTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/BodyValidationTest.java @@ -14,6 +14,10 @@ */ package org.hyperledger.besu.ethereum.mainnet; +import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; @@ -21,7 +25,6 @@ import java.util.Arrays; import org.apache.tuweni.bytes.Bytes32; -import org.assertj.core.api.Assertions; import org.junit.Test; /** Tests for {@link BodyValidation}. */ @@ -33,17 +36,32 @@ public void calculateTransactionsRoot() throws IOException { final BlockHeader header = ValidationTestUtils.readHeader(block); final BlockBody body = ValidationTestUtils.readBody(block); final Bytes32 transactionRoot = BodyValidation.transactionsRoot(body.getTransactions()); - Assertions.assertThat(header.getTransactionsRoot()).isEqualTo(transactionRoot); + assertThat(header.getTransactionsRoot()).isEqualTo(transactionRoot); } } + @Test + public void calculateEmptyTransactionsRoot() { + assertThat(BodyValidation.transactionsRoot(emptyList())).isEqualTo(Hash.EMPTY_TRIE_HASH); + } + + @Test + public void calculateEmptyWithdrawalsRoot() { + assertThat(BodyValidation.withdrawalsRoot(emptyList())).isEqualTo(Hash.EMPTY_TRIE_HASH); + } + + @Test + public void calculateEmptyReceiptsRoot() { + assertThat(BodyValidation.receiptsRoot(emptyList())).isEqualTo(Hash.EMPTY_TRIE_HASH); + } + @Test public void calculateOmmersHash() throws IOException { for (final int block : Arrays.asList(300006, 4400002)) { final BlockHeader header = ValidationTestUtils.readHeader(block); final BlockBody body = ValidationTestUtils.readBody(block); final Bytes32 ommersHash = BodyValidation.ommersHash(body.getOmmers()); - Assertions.assertThat(header.getOmmersHash()).isEqualTo(ommersHash); + assertThat(header.getOmmersHash()).isEqualTo(ommersHash); } } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/KeccakHasherTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/KeccakHasherTest.java index 3ec7774be36..fe92d345172 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/KeccakHasherTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/KeccakHasherTest.java @@ -62,7 +62,8 @@ protected KeccakSealableBlockHeader( timestamp, extraData, baseFee, - random); + random, + Hash.EMPTY); } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockBodyValidatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockBodyValidatorTest.java new file mode 100644 index 00000000000..ded0d90454b --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockBodyValidatorTest.java @@ -0,0 +1,118 @@ +/* + * Copyright Hyperledger 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.mainnet; + +import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockDataGenerator; +import org.hyperledger.besu.evm.log.LogsBloomFilter; + +import java.util.Optional; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class MainnetBlockBodyValidatorTest { + + @Mock private ProtocolSchedule protocolSchedule; + @Mock private ProtocolSpec protocolSpec; + private final BlockDataGenerator.BlockOptions validBlockOptions = + BlockDataGenerator.BlockOptions.create() + .setReceiptsRoot(Hash.EMPTY_TRIE_HASH) + .setGasUsed(0) + .setLogsBloom(LogsBloomFilter.empty()) + .hasOmmers(false); + private MainnetBlockBodyValidator validator; + + @Before + public void setup() { + when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); + validator = new MainnetBlockBodyValidator(protocolSchedule); + } + + @Test + public void validateBodyLight_ReturnsTrue_WhenWithdrawalsProhibited_WithNullWithdrawals() { + whenWithdrawalsProhibited(); + + final boolean result = + validator.validateBodyLight(null, blockWithNullWithdrawals(), emptyList(), null); + + assertThat(result).isTrue(); + } + + @Test + public void validateBodyLight_ReturnsFalse_WhenWithdrawalsProhibited_WithNonNullWithdrawals() { + whenWithdrawalsProhibited(); + + final boolean result = + validator.validateBodyLight(null, blockWithWithdrawals(), emptyList(), null); + + assertThat(result).isFalse(); + } + + @Test + public void validateBodyLight_ReturnsFalse_WhenWithdrawalsAllowed_WithNullWithdrawals() { + whenWithdrawalsAllowed(); + + final boolean result = + validator.validateBodyLight(null, blockWithNullWithdrawals(), emptyList(), null); + + assertThat(result).isFalse(); + } + + @Test + public void validateBodyLight_ReturnsTrue_WhenWithdrawalsAllowed_WithNonNullWithdrawals() { + whenWithdrawalsAllowed(); + + final boolean result = + validator.validateBodyLight(null, blockWithWithdrawals(), emptyList(), null); + + assertThat(result).isTrue(); + } + + private void whenWithdrawalsProhibited() { + when(protocolSpec.getWithdrawalsValidator()) + .thenReturn(new WithdrawalsValidator.ProhibitedWithdrawals()); + } + + private void whenWithdrawalsAllowed() { + when(protocolSpec.getWithdrawalsValidator()) + .thenReturn(new WithdrawalsValidator.AllowedWithdrawals()); + } + + private Block blockWithNullWithdrawals() { + return getBlock(validBlockOptions.setWithdrawals(Optional.empty())); + } + + private Block blockWithWithdrawals() { + return getBlock( + validBlockOptions + .setWithdrawalsRoot(Hash.EMPTY_TRIE_HASH) + .setWithdrawals(Optional.of(emptyList()))); + } + + private Block getBlock(final BlockDataGenerator.BlockOptions options) { + return new BlockDataGenerator().block(options); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidatorTest.java index 0fb4016acc8..a42d41b1fa3 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidatorTest.java @@ -45,8 +45,8 @@ import java.math.BigInteger; import java.util.Optional; import java.util.Set; +import java.util.function.Supplier; -import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import org.apache.tuweni.bytes.Bytes; import org.junit.Test; @@ -259,7 +259,8 @@ public void shouldRejectTransactionWithMaxPriorityFeeGreaterThanMaxFee() { false, Optional.of(BigInteger.ONE), Set.of(TransactionType.values()), - defaultGoQuorumCompatibilityMode); + defaultGoQuorumCompatibilityMode, + Integer.MAX_VALUE); validator.setTransactionFilter(transactionFilter(true)); final Transaction transaction = @@ -342,7 +343,8 @@ public void shouldAcceptOnlyTransactionsInAcceptedTransactionTypes() { false, Optional.of(BigInteger.ONE), Set.of(TransactionType.FRONTIER), - defaultGoQuorumCompatibilityMode); + defaultGoQuorumCompatibilityMode, + Integer.MAX_VALUE); final MainnetTransactionValidator eip1559Validator = new MainnetTransactionValidator( @@ -351,7 +353,8 @@ public void shouldAcceptOnlyTransactionsInAcceptedTransactionTypes() { false, Optional.of(BigInteger.ONE), Set.of(TransactionType.FRONTIER, TransactionType.EIP1559), - defaultGoQuorumCompatibilityMode); + defaultGoQuorumCompatibilityMode, + Integer.MAX_VALUE); final Transaction transaction = new TransactionTestFixture() @@ -383,7 +386,8 @@ public void shouldRejectTransactionIfEIP1559TransactionGasPriceLessBaseFee() { false, Optional.of(BigInteger.ONE), Set.of(TransactionType.FRONTIER, TransactionType.EIP1559), - defaultGoQuorumCompatibilityMode); + defaultGoQuorumCompatibilityMode, + Integer.MAX_VALUE); final Transaction transaction = new TransactionTestFixture() .type(TransactionType.EIP1559) @@ -406,7 +410,8 @@ public void shouldAcceptZeroGasPriceTransactionIfBaseFeeIsZero() { false, Optional.of(BigInteger.ONE), Set.of(TransactionType.FRONTIER, TransactionType.EIP1559), - defaultGoQuorumCompatibilityMode); + defaultGoQuorumCompatibilityMode, + Integer.MAX_VALUE); final Transaction transaction = new TransactionTestFixture() .type(TransactionType.EIP1559) @@ -428,7 +433,8 @@ public void shouldAcceptValidEIP1559() { false, Optional.of(BigInteger.ONE), Set.of(TransactionType.FRONTIER, TransactionType.EIP1559), - defaultGoQuorumCompatibilityMode); + defaultGoQuorumCompatibilityMode, + Integer.MAX_VALUE); final Transaction transaction = new TransactionTestFixture() .maxPriorityFeePerGas(Optional.of(Wei.of(1))) @@ -452,7 +458,8 @@ public void shouldValidate1559TransactionWithPriceLowerThanBaseFeeForTransaction false, Optional.of(BigInteger.ONE), Set.of(TransactionType.FRONTIER, TransactionType.EIP1559), - defaultGoQuorumCompatibilityMode); + defaultGoQuorumCompatibilityMode, + Integer.MAX_VALUE); final Transaction transaction = new TransactionTestFixture() .maxPriorityFeePerGas(Optional.of(Wei.of(1))) @@ -468,6 +475,33 @@ public void shouldValidate1559TransactionWithPriceLowerThanBaseFeeForTransaction .isEqualTo(ValidationResult.valid()); } + @Test + public void shouldRejectTooLargeInitcode() { + final MainnetTransactionValidator validator = + new MainnetTransactionValidator( + gasCalculator, + FeeMarket.london(0L), + false, + Optional.of(BigInteger.ONE), + Set.of(TransactionType.FRONTIER, TransactionType.EIP1559), + defaultGoQuorumCompatibilityMode, + 0xc000); + + var bigPayload = + new TransactionTestFixture() + .payload(Bytes.fromHexString("0x" + "00".repeat(0xc001))) + .chainId(Optional.of(BigInteger.ONE)) + .createTransaction(senderKeys); + var validationResult = + validator.validate(bigPayload, Optional.empty(), transactionValidationParams); + + assertThat(validationResult.isValid()).isFalse(); + assertThat(validationResult.getInvalidReason()) + .isEqualTo(TransactionInvalidReason.INITCODE_TOO_LARGE); + assertThat(validationResult.getErrorMessage()) + .isEqualTo("Initcode size of 49153 exceeds maximum size of 49152"); + } + @Test public void goQuorumCompatibilityModeRejectNonZeroGasPrice() { final MainnetTransactionValidator validator = diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessorTest.java index 5e54e557968..6db678450b2 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessorTest.java @@ -154,7 +154,7 @@ public void mustPerformRehydration() { when(blockchain.getBlockByHash(any())).thenReturn(Optional.of(firstBlock)); when(blockchain.getBlockHeader(any())).thenReturn(Optional.of(firstBlock.getHeader())); final ProtocolSpec protocolSpec = mockProtocolSpec(); - when(protocolSchedule.getByBlockNumber(anyLong())).thenReturn(protocolSpec); + when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); when(publicWorldStateArchive.getMutable(any(), any())) .thenReturn(Optional.of(mutableWorldState)); final MutableWorldState mockPrivateStateArchive = mockPrivateStateArchive(); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilderTest.java index 501279d3749..28c01281be4 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilderTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilderTest.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.mainnet; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -29,6 +30,7 @@ import java.util.function.Function; import java.util.stream.Collectors; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -39,8 +41,64 @@ public class ProtocolScheduleBuilderTest { @Mock GenesisConfigOptions configOptions; - @Mock private Function modifier; + private static final BigInteger CHAIN_ID = BigInteger.ONE; + private ProtocolScheduleBuilder builder; + + @Before + public void setup() { + builder = + new ProtocolScheduleBuilder( + configOptions, + CHAIN_ID, + ProtocolSpecAdapters.create(0, Function.identity()), + new PrivacyParameters(), + false, + false, + EvmConfiguration.DEFAULT); + } + + @Test + public void createProtocolScheduleInOrder() { + when(configOptions.getHomesteadBlockNumber()).thenReturn(OptionalLong.of(1L)); + when(configOptions.getDaoForkBlock()).thenReturn(OptionalLong.of(2L)); + when(configOptions.getByzantiumBlockNumber()).thenReturn(OptionalLong.of(13L)); + when(configOptions.getMergeNetSplitBlockNumber()).thenReturn(OptionalLong.of(15L)); + final ProtocolSchedule protocolSchedule = builder.createProtocolSchedule(); + + assertThat(protocolSchedule.getChainId()).contains(CHAIN_ID); + assertThat(protocolSchedule.getByBlockNumber(0).getName()).isEqualTo("Frontier"); + assertThat(protocolSchedule.getByBlockNumber(1).getName()).isEqualTo("Homestead"); + assertThat(protocolSchedule.getByBlockNumber(2).getName()).isEqualTo("DaoRecoveryInit"); + assertThat(protocolSchedule.getByBlockNumber(3).getName()).isEqualTo("DaoRecoveryTransition"); + assertThat(protocolSchedule.getByBlockNumber(12).getName()).isEqualTo("Homestead"); + assertThat(protocolSchedule.getByBlockNumber(13).getName()).isEqualTo("Byzantium"); + assertThat(protocolSchedule.getByBlockNumber(14).getName()).isEqualTo("Byzantium"); + assertThat(protocolSchedule.getByBlockNumber(15).getName()).isEqualTo("ParisFork"); + assertThat(protocolSchedule.getByBlockNumber(50).getName()).isEqualTo("ParisFork"); + } + + @Test + public void createProtocolScheduleOverlappingUsesLatestFork() { + when(configOptions.getHomesteadBlockNumber()).thenReturn(OptionalLong.of(0L)); + when(configOptions.getByzantiumBlockNumber()).thenReturn(OptionalLong.of(0L)); + final ProtocolSchedule protocolSchedule = builder.createProtocolSchedule(); + + assertThat(protocolSchedule.getChainId()).contains(CHAIN_ID); + assertThat(protocolSchedule.getByBlockNumber(0).getName()).isEqualTo("Byzantium"); + assertThat(protocolSchedule.getByBlockNumber(1).getName()).isEqualTo("Byzantium"); + } + + @Test + public void createProtocolScheduleOutOfOrderThrows() { + when(configOptions.getDaoForkBlock()).thenReturn(OptionalLong.of(0L)); + when(configOptions.getArrowGlacierBlockNumber()).thenReturn(OptionalLong.of(12L)); + when(configOptions.getGrayGlacierBlockNumber()).thenReturn(OptionalLong.of(11L)); + assertThatThrownBy(() -> builder.createProtocolSchedule()) + .isInstanceOf(RuntimeException.class) + .hasMessage( + "Genesis Config Error: 'GrayGlacier' is scheduled for block 11 but it must be on or after block 12."); + } @Test public void modifierInsertedBetweenBlocksIsAppliedToLaterAndCreatesInterimMilestone() { @@ -52,7 +110,7 @@ public void modifierInsertedBetweenBlocksIsAppliedToLaterAndCreatesInterimMilest final ProtocolScheduleBuilder builder = new ProtocolScheduleBuilder( configOptions, - BigInteger.ONE, + CHAIN_ID, ProtocolSpecAdapters.create(2, modifier), new PrivacyParameters(), false, @@ -81,7 +139,7 @@ public void modifierPastEndOfDefinedMilestonesGetsItsOwnMilestoneCreated() { final ProtocolScheduleBuilder builder = new ProtocolScheduleBuilder( configOptions, - BigInteger.ONE, + CHAIN_ID, ProtocolSpecAdapters.create(2, modifier), new PrivacyParameters(), false, @@ -110,7 +168,7 @@ public void modifierOnDefinedMilestoneIsAppliedButDoesNotGetAnExtraMilestoneCrea final ProtocolScheduleBuilder builder = new ProtocolScheduleBuilder( configOptions, - BigInteger.ONE, + CHAIN_ID, ProtocolSpecAdapters.create(5, modifier), new PrivacyParameters(), false, diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleTest.java index 39492f6fe5e..b37235e2ce5 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleTest.java @@ -16,6 +16,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.ethereum.core.BlockHeader; import java.math.BigInteger; import java.util.Optional; @@ -67,4 +70,21 @@ public void conflictingSchedules() { protocolSchedule.putMilestone(0, spec2); assertThat(protocolSchedule.getByBlockNumber(0)).isSameAs(spec2); } + + @Test + public void getByBlockHeader_defaultMethodShouldUseGetByBlockNumber() { + final ProtocolSpec spec1 = mock(ProtocolSpec.class); + final ProtocolSpec spec2 = mock(ProtocolSpec.class); + + final MutableProtocolSchedule protocolSchedule = new MutableProtocolSchedule(CHAIN_ID); + protocolSchedule.putMilestone(0, spec1); + protocolSchedule.putMilestone(1, spec2); + + final BlockHeader blockHeader = mock(BlockHeader.class); + when(blockHeader.getNumber()).thenReturn(1L); + + final ProtocolSpec spec = protocolSchedule.getByBlockHeader(blockHeader); + + assertThat(spec).isEqualTo(spec2); + } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/TimestampScheduleBuilderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/TimestampScheduleBuilderTest.java new file mode 100644 index 00000000000..035f3ae6fc4 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/TimestampScheduleBuilderTest.java @@ -0,0 +1,141 @@ +/* + * Copyright Hyperledger 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.mainnet; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.hyperledger.besu.config.StubGenesisConfigOptions; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.evm.internal.EvmConfiguration; + +import java.math.BigInteger; +import java.util.function.Function; + +import org.junit.Before; +import org.junit.Test; + +public class TimestampScheduleBuilderTest { + + private static final BigInteger chainId = BigInteger.ONE; + private static final BigInteger defaultChainId = BigInteger.ONE; + private static final PrivacyParameters privacyParameters = new PrivacyParameters(); + private static final EvmConfiguration evmConfiguration = EvmConfiguration.DEFAULT; + private static final BlockHeader BLOCK_HEADER = + new BlockHeaderTestFixture().timestamp(1L).buildHeader(); + private TimestampScheduleBuilder builder; + private StubGenesisConfigOptions config; + + private final Function modifier = Function.identity(); + + private final long FIRST_TIMESTAMP_FORK = 1L; + + @Before + public void setup() { + config = new StubGenesisConfigOptions(); + config.chainId(chainId); + boolean isRevertReasonEnabled = false; + boolean quorumCompatibilityMode = false; + builder = + new TimestampScheduleBuilder( + config, + defaultChainId, + ProtocolSpecAdapters.create(FIRST_TIMESTAMP_FORK, modifier), + privacyParameters, + isRevertReasonEnabled, + quorumCompatibilityMode, + evmConfiguration); + } + + @Test + public void createTimestampScheduleInOrder() { + config.shanghaiTime(FIRST_TIMESTAMP_FORK); + config.cancunTime(3); + final TimestampSchedule timestampSchedule = builder.createTimestampSchedule(); + + assertThat(timestampSchedule.getChainId()).contains(chainId); + assertThat(timestampSchedule.getByTimestamp(0)).isEmpty(); + assertThat(timestampSchedule.getByTimestamp(1)) + .isPresent() + .map(ProtocolSpec::getName) + .hasValue("Shanghai"); + assertThat(timestampSchedule.getByTimestamp(2)) + .isPresent() + .map(ProtocolSpec::getName) + .hasValue("Shanghai"); + assertThat(timestampSchedule.getByTimestamp(3)) + .isPresent() + .map(ProtocolSpec::getName) + .hasValue("Cancun"); + assertThat(timestampSchedule.getByTimestamp(4)) + .isPresent() + .map(ProtocolSpec::getName) + .hasValue("Cancun"); + } + + @Test + public void createTimestampScheduleOverlappingUsesLatestFork() { + config.shanghaiTime(0); + config.cancunTime(0); + final TimestampSchedule timestampSchedule = builder.createTimestampSchedule(); + + assertThat(timestampSchedule.getChainId()).contains(chainId); + assertThat(timestampSchedule.getByTimestamp(0)) + .isPresent() + .map(ProtocolSpec::getName) + .hasValue("Cancun"); + assertThat(timestampSchedule.getByTimestamp(1)) + .isPresent() + .map(ProtocolSpec::getName) + .hasValue("Cancun"); + } + + @Test + public void createTimestampScheduleOutOfOrderThrows() { + config.shanghaiTime(3); + config.cancunTime(2); + assertThatThrownBy(() -> builder.createTimestampSchedule()) + .isInstanceOf(RuntimeException.class) + .hasMessage( + "Genesis Config Error: 'Cancun' is scheduled for timestamp 2 but it must be on or after timestamp 3."); + } + + @Test + public void getByBlockHeader_whenSpecFound() { + config.shanghaiTime(FIRST_TIMESTAMP_FORK); + final TimestampSchedule schedule = builder.createTimestampSchedule(); + + assertThat(schedule.getByBlockHeader(BLOCK_HEADER)).isNotNull(); + } + + @Test + public void getByBlockHeader_whenSpecNotFoundReturnsNull() { + config.shanghaiTime(2L); + builder = + new TimestampScheduleBuilder( + config, + defaultChainId, + ProtocolSpecAdapters.create(2L, modifier), + privacyParameters, + false, + false, + evmConfiguration); + final TimestampSchedule schedule = builder.createTimestampSchedule(); + + assertThat(schedule.getByBlockHeader(BLOCK_HEADER)).isNull(); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ValidationTestUtils.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ValidationTestUtils.java index 25a74a518c9..1b221bb1e12 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ValidationTestUtils.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ValidationTestUtils.java @@ -18,11 +18,13 @@ import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; import org.hyperledger.besu.ethereum.rlp.RLPInput; import java.io.IOException; import java.util.List; +import java.util.Optional; import com.google.common.io.Resources; import org.apache.tuweni.bytes.Bytes; @@ -52,7 +54,12 @@ public static BlockBody readBody(final long num) throws IOException { final List transactions = input.readList(Transaction::readFrom); final List ommers = input.readList(rlp -> BlockHeader.readFrom(rlp, new MainnetBlockHeaderFunctions())); - return new BlockBody(transactions, ommers); + return new BlockBody( + transactions, + ommers, + input.isEndOfCurrentList() + ? Optional.empty() + : Optional.of(input.readList(Withdrawal::readFrom))); } public static Block readBlock(final long num) throws IOException { @@ -67,7 +74,13 @@ public static Block readBlock(final long num) throws IOException { final List transactions = input.readList(Transaction::readFrom); final List ommers = input.readList(rlp -> BlockHeader.readFrom(rlp, new MainnetBlockHeaderFunctions())); - final BlockBody body = new BlockBody(transactions, ommers); + final BlockBody body = + new BlockBody( + transactions, + ommers, + input.isEndOfCurrentList() + ? Optional.empty() + : Optional.of(input.readList(Withdrawal::readFrom))); return new Block(header, body); } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/operations/Create2OperationTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/operations/Create2OperationTest.java index 359eb288f20..16427768baf 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/operations/Create2OperationTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/operations/Create2OperationTest.java @@ -15,6 +15,8 @@ package org.hyperledger.besu.ethereum.vm.operations; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.MainnetEVMs.DEV_NET_CHAIN_ID; +import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.CODE_TOO_LARGE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -25,42 +27,65 @@ import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.vm.BlockHashLookup; import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.MainnetEVMs; import org.hyperledger.besu.evm.account.MutableAccount; import org.hyperledger.besu.evm.code.CodeFactory; +import org.hyperledger.besu.evm.frame.BlockValues; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.log.Log; import org.hyperledger.besu.evm.operation.Create2Operation; import org.hyperledger.besu.evm.operation.Operation.OperationResult; +import org.hyperledger.besu.evm.processor.ContractCreationProcessor; +import org.hyperledger.besu.evm.tracing.OperationTracer; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.hyperledger.besu.evm.worldstate.WrappedEvmAccount; import java.util.ArrayDeque; +import java.util.List; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; -@RunWith(Parameterized.class) public class Create2OperationTest { - private final String sender; - private final String salt; - private final String code; - private final String expectedAddress; - private final int expectedGas; private MessageFrame messageFrame; private final WorldUpdater worldUpdater = mock(WorldUpdater.class); private final WrappedEvmAccount account = mock(WrappedEvmAccount.class); private final MutableAccount mutableAccount = mock(MutableAccount.class); private final EVM evm = mock(EVM.class); + private final WrappedEvmAccount newAccount = mock(WrappedEvmAccount.class); + private final MutableAccount newMutableAccount = mock(MutableAccount.class); + private final Create2Operation operation = - new Create2Operation(new ConstantinopleGasCalculator()); + new Create2Operation(new ConstantinopleGasCalculator(), Integer.MAX_VALUE); + + private final Create2Operation maxInitCodeOperation = + new Create2Operation( + new ConstantinopleGasCalculator(), MainnetEVMs.SHANGHAI_INIT_CODE_SIZE_LIMIT); + + private static final String TOPIC = + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; // 32 FFs + private static final Bytes SIMPLE_CREATE = + Bytes.fromHexString( + "0x" + + "7f" // push32 + + TOPIC + + "6000" // PUSH1 0x00 + + "6000" // PUSH1 0x00 + + "A1" // LOG1 + + "6000" // PUSH1 0x00 + + "6000" // PUSH1 0x00 + + "F3" // RETURN + ); + public static final String SENDER = "0xdeadc0de00000000000000000000000000000000"; + private static final int SHANGHAI_CREATE_GAS = 41240 + (0xc000 / 32) * 6; - @Parameters(name = "sender: {0}, salt: {1}, code: {2}") public static Object[][] params() { return new Object[][] { { @@ -115,21 +140,8 @@ public static Object[][] params() { }; } - public Create2OperationTest( - final String sender, - final String salt, - final String code, - final String expectedAddress, - final int expectedGas) { - this.sender = sender; - this.salt = salt; - this.code = code; - this.expectedAddress = expectedAddress; - this.expectedGas = expectedGas; - } + public void setUp(final String sender, final String salt, final String code) { - @Before - public void setUp() { final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); final Bytes codeBytes = Bytes.fromHexString(code); final UInt256 memoryLength = UInt256.valueOf(codeBytes.size()); @@ -172,16 +184,124 @@ public void setUp() { invocation.getArgument(1), invocation.getArgument(0), 0, true)); } - @Test - public void shouldCalculateAddress() { + @ParameterizedTest + @MethodSource("params") + void shouldCalculateAddress( + final String sender, + final String salt, + final String code, + final String expectedAddress, + final int ignoredExpectedGas) { + setUp(sender, salt, code); final Address targetContractAddress = operation.targetContractAddress(messageFrame); assertThat(targetContractAddress).isEqualTo(Address.fromHexString(expectedAddress)); } - @Test - public void shouldCalculateGasPrice() { + @ParameterizedTest + @MethodSource("params") + void shouldCalculateGasPrice( + final String sender, + final String salt, + final String code, + final String ignoredExpectedAddress, + final int expectedGas) { + setUp(sender, salt, code); final OperationResult result = operation.execute(messageFrame, evm); assertThat(result.getHaltReason()).isNull(); assertThat(result.getGasCost()).isEqualTo(expectedGas); } + + @Test + void shanghaiMaxInitCodeSizeCreate() { + final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); + final UInt256 memoryLength = UInt256.fromHexString("0xc000"); + final ArrayDeque messageFrameStack = new ArrayDeque<>(); + final MessageFrame messageFrame = + testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1, messageFrameStack); + + when(account.getMutable()).thenReturn(mutableAccount); + when(account.getNonce()).thenReturn(55L); + when(mutableAccount.getBalance()).thenReturn(Wei.ZERO); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getSenderAccount(any())).thenReturn(account); + when(worldUpdater.getOrCreate(any())).thenReturn(newAccount); + when(newAccount.getMutable()).thenReturn(newMutableAccount); + when(newMutableAccount.getCode()).thenReturn(Bytes.EMPTY); + when(worldUpdater.updater()).thenReturn(worldUpdater); + + final EVM evm = MainnetEVMs.shanghai(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); + var result = maxInitCodeOperation.execute(messageFrame, evm); + final MessageFrame createFrame = messageFrameStack.peek(); + final ContractCreationProcessor ccp = + new ContractCreationProcessor(evm.getGasCalculator(), evm, false, List.of(), 0, List.of()); + ccp.process(createFrame, OperationTracer.NO_TRACING); + + final Log log = createFrame.getLogs().get(0); + final String calculatedTopic = log.getTopics().get(0).toUnprefixedHexString(); + assertThat(calculatedTopic).isEqualTo(TOPIC); + assertThat(result.getGasCost()).isEqualTo(SHANGHAI_CREATE_GAS); + } + + @Test + void shanghaiMaxInitCodeSizePlus1Create() { + final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); + final UInt256 memoryLength = UInt256.fromHexString("0xc001"); + final ArrayDeque messageFrameStack = new ArrayDeque<>(); + final MessageFrame messageFrame = + testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1, messageFrameStack); + + when(account.getMutable()).thenReturn(mutableAccount); + when(account.getNonce()).thenReturn(55L); + when(mutableAccount.getBalance()).thenReturn(Wei.ZERO); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getSenderAccount(any())).thenReturn(account); + when(worldUpdater.getOrCreate(any())).thenReturn(newAccount); + when(newAccount.getMutable()).thenReturn(newMutableAccount); + when(newMutableAccount.getCode()).thenReturn(Bytes.EMPTY); + when(worldUpdater.updater()).thenReturn(worldUpdater); + + final EVM evm = MainnetEVMs.shanghai(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); + var result = maxInitCodeOperation.execute(messageFrame, evm); + assertThat(result.getHaltReason()).isEqualTo(CODE_TOO_LARGE); + } + + @NotNull + private MessageFrame testMemoryFrame( + final UInt256 memoryOffset, + final UInt256 memoryLength, + final UInt256 value, + final int depth, + final ArrayDeque messageFrameStack) { + final MessageFrame messageFrame = + MessageFrame.builder() + .type(MessageFrame.Type.CONTRACT_CREATION) + .contract(Address.ZERO) + .inputData(Bytes.EMPTY) + .sender(Address.fromHexString(SENDER)) + .value(Wei.ZERO) + .apparentValue(Wei.ZERO) + .code(CodeFactory.createCode(SIMPLE_CREATE, Hash.hash(SIMPLE_CREATE), 0, true)) + .depth(depth) + .completer(__ -> {}) + .address(Address.fromHexString(SENDER)) + .blockHashLookup(n -> Hash.hash(Bytes.ofUnsignedLong(n))) + .blockValues(mock(BlockValues.class)) + .gasPrice(Wei.ZERO) + .messageFrameStack(messageFrameStack) + .miningBeneficiary(Address.ZERO) + .originator(Address.ZERO) + .initialGas(100000L) + .worldUpdater(worldUpdater) + .build(); + messageFrame.pushStackItem(Bytes.EMPTY); + messageFrame.pushStackItem(memoryLength); + messageFrame.pushStackItem(memoryOffset); + messageFrame.pushStackItem(value); + messageFrame.expandMemory(0, 500); + messageFrame.writeMemory( + memoryOffset.trimLeadingZeros().toInt(), SIMPLE_CREATE.size(), SIMPLE_CREATE); + return messageFrame; + } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/operations/CreateOperationTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/operations/CreateOperationTest.java index 2eb2f4a2b2d..958aff281a4 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/operations/CreateOperationTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/operations/CreateOperationTest.java @@ -16,8 +16,8 @@ package org.hyperledger.besu.ethereum.vm.operations; import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSpecs.SHANDONG_CONTRACT_SIZE_LIMIT; import static org.hyperledger.besu.evm.MainnetEVMs.DEV_NET_CHAIN_ID; +import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.CODE_TOO_LARGE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -25,12 +25,11 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; -import org.hyperledger.besu.ethereum.vm.BlockHashLookup; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.MainnetEVMs; import org.hyperledger.besu.evm.account.MutableAccount; import org.hyperledger.besu.evm.code.CodeFactory; +import org.hyperledger.besu.evm.frame.BlockValues; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator; import org.hyperledger.besu.evm.internal.EvmConfiguration; @@ -56,7 +55,11 @@ public class CreateOperationTest { private final WrappedEvmAccount newAccount = mock(WrappedEvmAccount.class); private final MutableAccount mutableAccount = mock(MutableAccount.class); private final MutableAccount newMutableAccount = mock(MutableAccount.class); - private final CreateOperation operation = new CreateOperation(new ConstantinopleGasCalculator()); + private final CreateOperation operation = + new CreateOperation(new ConstantinopleGasCalculator(), Integer.MAX_VALUE); + private final CreateOperation maxInitCodeOperation = + new CreateOperation( + new ConstantinopleGasCalculator(), MainnetEVMs.SHANGHAI_INIT_CODE_SIZE_LIMIT); private static final String TOPIC = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; // 32 FFs @@ -73,7 +76,7 @@ public class CreateOperationTest { + "F3" // RETURN ); public static final String SENDER = "0xdeadc0de00000000000000000000000000000000"; - private static final int SHANDONG_CREATE_GAS = 41240; + private static final int SHANGHAI_CREATE_GAS = 41240; @Test public void createFromMemoryMutationSafe() { @@ -179,9 +182,9 @@ public void notEnoughValue() { } @Test - public void shandongMaxInitCodeSizeCreate() { + public void shanghaiMaxInitCodeSizeCreate() { final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); - final UInt256 memoryLength = UInt256.valueOf(SHANDONG_CONTRACT_SIZE_LIMIT); + final UInt256 memoryLength = UInt256.fromHexString("0xc000"); final ArrayDeque messageFrameStack = new ArrayDeque<>(); final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1, messageFrameStack); @@ -197,8 +200,8 @@ public void shandongMaxInitCodeSizeCreate() { when(newMutableAccount.getCode()).thenReturn(Bytes.EMPTY); when(worldUpdater.updater()).thenReturn(worldUpdater); - final EVM evm = MainnetEVMs.shandong(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); - var result = operation.execute(messageFrame, evm); + final EVM evm = MainnetEVMs.shanghai(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); + var result = maxInitCodeOperation.execute(messageFrame, evm); final MessageFrame createFrame = messageFrameStack.peek(); final ContractCreationProcessor ccp = new ContractCreationProcessor(evm.getGasCalculator(), evm, false, List.of(), 0, List.of()); @@ -207,13 +210,13 @@ public void shandongMaxInitCodeSizeCreate() { final Log log = createFrame.getLogs().get(0); final String calculatedTopic = log.getTopics().get(0).toUnprefixedHexString(); assertThat(calculatedTopic).isEqualTo(TOPIC); - assertThat(result.getGasCost()).isEqualTo(SHANDONG_CREATE_GAS); + assertThat(result.getGasCost()).isEqualTo(SHANGHAI_CREATE_GAS); } @Test - public void shandongMaxInitCodeSizePlus1Create() { + public void shanghaiMaxInitCodeSizePlus1Create() { final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); - final UInt256 memoryLength = UInt256.valueOf(SHANDONG_CONTRACT_SIZE_LIMIT + 1); + final UInt256 memoryLength = UInt256.fromHexString("0xc001"); final ArrayDeque messageFrameStack = new ArrayDeque<>(); final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1, messageFrameStack); @@ -229,10 +232,9 @@ public void shandongMaxInitCodeSizePlus1Create() { when(newMutableAccount.getCode()).thenReturn(Bytes.EMPTY); when(worldUpdater.updater()).thenReturn(worldUpdater); - final EVM evm = MainnetEVMs.shandong(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); - var result = operation.execute(messageFrame, evm); - assertThat(messageFrame.getStackItem(0)).isEqualTo(UInt256.ZERO); - assertThat(result.getGasCost()).isEqualTo(SHANDONG_CREATE_GAS); + final EVM evm = MainnetEVMs.shanghai(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); + var result = maxInitCodeOperation.execute(messageFrame, evm); + assertThat(result.getHaltReason()).isEqualTo(CODE_TOO_LARGE); } @NotNull @@ -254,8 +256,8 @@ private MessageFrame testMemoryFrame( .depth(depth) .completer(__ -> {}) .address(Address.fromHexString(SENDER)) - .blockHashLookup(mock(BlockHashLookup.class)) - .blockValues(mock(ProcessableBlockHeader.class)) + .blockHashLookup(n -> Hash.hash(Bytes.ofUnsignedLong(n))) + .blockValues(mock(BlockValues.class)) .gasPrice(Wei.ZERO) .messageFrameStack(messageFrameStack) .miningBeneficiary(Address.ZERO) diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManager.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManager.java index 2a7a64646a8..9f14e99d9fb 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManager.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManager.java @@ -155,6 +155,7 @@ public EthProtocolManager( new ForkIdManager( blockchain, Collections.emptyList(), + Collections.emptyList(), ethereumWireProtocolConfiguration.isLegacyEth64ForkIdEnabled())); } @@ -171,7 +172,8 @@ public EthProtocolManager( final Optional mergePeerFilter, final SynchronizerConfiguration synchronizerConfiguration, final EthScheduler scheduler, - final List forks) { + final List blockNumberForks, + final List timestampForks) { this( blockchain, networkId, @@ -186,7 +188,10 @@ public EthProtocolManager( synchronizerConfiguration, scheduler, new ForkIdManager( - blockchain, forks, ethereumWireProtocolConfiguration.isLegacyEth64ForkIdEnabled())); + blockchain, + blockNumberForks, + timestampForks, + ethereumWireProtocolConfiguration.isLegacyEth64ForkIdEnabled())); } public EthContext ethContext() { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetBodiesFromPeerTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetBodiesFromPeerTask.java index 9eb0f77f5d4..545fd21066e 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetBodiesFromPeerTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetBodiesFromPeerTask.java @@ -21,6 +21,7 @@ import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; import org.hyperledger.besu.ethereum.eth.manager.PendingPeerRequest; @@ -127,43 +128,49 @@ protected Optional> processResponse( return Optional.of(blocks); } - private static class BodyIdentifier { + static class BodyIdentifier { private final Bytes32 transactionsRoot; private final Bytes32 ommersHash; + private final Bytes32 withdrawalsRoot; - public BodyIdentifier(final Bytes32 transactionsRoot, final Bytes32 ommersHash) { + public BodyIdentifier( + final Bytes32 transactionsRoot, final Bytes32 ommersHash, final Bytes32 withdrawalsRoot) { this.transactionsRoot = transactionsRoot; this.ommersHash = ommersHash; + this.withdrawalsRoot = withdrawalsRoot; } public BodyIdentifier(final BlockBody body) { - this(body.getTransactions(), body.getOmmers()); + this(body.getTransactions(), body.getOmmers(), body.getWithdrawals().orElse(null)); } - public BodyIdentifier(final List transactions, final List ommers) { - this(BodyValidation.transactionsRoot(transactions), BodyValidation.ommersHash(ommers)); + public BodyIdentifier( + final List transactions, + final List ommers, + final List withdrawals) { + this( + BodyValidation.transactionsRoot(transactions), + BodyValidation.ommersHash(ommers), + withdrawals == null ? Hash.EMPTY : BodyValidation.withdrawalsRoot(withdrawals)); } public BodyIdentifier(final BlockHeader header) { - this(header.getTransactionsRoot(), header.getOmmersHash()); + this(header.getTransactionsRoot(), header.getOmmersHash(), header.getWithdrawalsRoot()); } @Override public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - final BodyIdentifier that = (BodyIdentifier) o; + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BodyIdentifier that = (BodyIdentifier) o; return Objects.equals(transactionsRoot, that.transactionsRoot) - && Objects.equals(ommersHash, that.ommersHash); + && Objects.equals(ommersHash, that.ommersHash) + && Objects.equals(withdrawalsRoot, that.withdrawalsRoot); } @Override public int hashCode() { - return Objects.hash(transactionsRoot, ommersHash); + return Objects.hash(transactionsRoot, ommersHash, withdrawalsRoot); } } } 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 2a895c19938..97f5d09afed 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 @@ -260,12 +260,12 @@ public ProtocolContext getProtocolContext() { return protocolContext; } - public BlockValidator getBlockValidator(final long blockNumber) { - return protocolSchedule.getByBlockNumber(blockNumber).getBlockValidator(); + public BlockValidator getBlockValidator(final BlockHeader blockHeader) { + return protocolSchedule.getByBlockHeader(blockHeader).getBlockValidator(); } public BlockValidator getBlockValidatorForBlock(final Block block) { - return getBlockValidator(block.getHeader().getNumber()); + return getBlockValidator(block.getHeader()); } public boolean isReady() { @@ -344,7 +344,7 @@ protected void possiblyMoveHead(final Block lastSavedBlock) { return; } - debugLambda(LOG, "Rewinding head to last saved block {}", lastSavedBlock::toLogString); + traceLambda(LOG, "Rewinding head to last saved block {}", lastSavedBlock::toLogString); blockchain.rewindToBlock(lastSavedBlock.getHash()); } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/ForkIdBackwardCompatibilityTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/ForkIdBackwardCompatibilityTest.java index a447d28c2d8..9df3157cfee 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/ForkIdBackwardCompatibilityTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/ForkIdBackwardCompatibilityTest.java @@ -23,6 +23,7 @@ import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; import org.apache.tuweni.bytes.Bytes; @@ -117,7 +118,8 @@ public static Collection data() { public void assertBackwardCompatibilityWorks() { LOG.info("Running test case {}", name); final ForkIdManager forkIdManager = - new ForkIdManager(mockBlockchain(genesisHash, head), forks, legacyEth64); + new ForkIdManager( + mockBlockchain(genesisHash, head), forks, Collections.emptyList(), legacyEth64); final ForkId legacyForkId = legacyEth64 ? new LegacyForkIdManager(mockBlockchain(genesisHash, head), forks).getLatestForkId() diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/ForkIdTestUtil.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/ForkIdTestUtil.java index c5e1b567c25..f549fc1b271 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/ForkIdTestUtil.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/ForkIdTestUtil.java @@ -41,9 +41,12 @@ public static Blockchain mockBlockchain( final Blockchain mockchain = mock(Blockchain.class); final BlockHeader mockHeader = mock(BlockHeader.class); final Block block = new Block(mockHeader, null); + final BlockHeader mockChainHeadHeader = mock(BlockHeader.class); when(mockchain.getGenesisBlock()).thenReturn(block); when(mockchain.getChainHeadBlockNumber()).thenReturn(chainHeightSupplier.getAsLong()); when(mockHeader.getHash()).thenReturn(Hash.fromHexString(genesisHash)); + when(mockchain.getChainHeadHeader()).thenReturn(mockChainHeadHeader); + when(mockChainHeadHeader.getNumber()).thenReturn(chainHeightSupplier.getAsLong()); return mockchain; } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EIP2124Test.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EIP2124Test.java index a172a74ad40..66c516d453a 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EIP2124Test.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EIP2124Test.java @@ -30,6 +30,7 @@ import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Optional; @@ -687,11 +688,12 @@ public static Collection data() { public void test() { LOG.info("Running test case {}", name); final ForkIdManager forkIdManager = - new ForkIdManager(mockBlockchain(network.hash, head), network.forks, false); + new ForkIdManager( + mockBlockchain(network.hash, head), network.forks, Collections.emptyList(), false); wantForkId.ifPresent( forkId -> assertThat(forkIdManager.getForkIdForChainHead()).isEqualTo(forkId)); wantForkIds.ifPresent( - forkIds -> assertThat(forkIdManager.getForkIds()).containsExactlyElementsOf(forkIds)); + forkIds -> assertThat(forkIdManager.getAllForkIds()).containsExactlyElementsOf(forkIds)); wantPeerCheckCase.ifPresent( peerCheckCase -> assertThat( diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTest.java index 2cf61453757..e05332c1084 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTest.java @@ -1116,7 +1116,8 @@ public void forkIdForChainHeadMayBeNull() { protocolContext.getWorldStateArchive(), transactionPool, EthProtocolConfiguration.defaultConfig(), - new ForkIdManager(blockchain, Collections.emptyList(), true))) { + new ForkIdManager( + blockchain, Collections.emptyList(), Collections.emptyList(), true))) { assertThat(ethManager.getForkIdAsBytesList()).isEmpty(); } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTestUtil.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTestUtil.java index de434104f6a..447910b4bb9 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTestUtil.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTestUtil.java @@ -92,7 +92,7 @@ public static EthProtocolManager create( mergePeerFilter, mock(SynchronizerConfiguration.class), ethScheduler, - new ForkIdManager(blockchain, Collections.emptyList(), false)); + new ForkIdManager(blockchain, Collections.emptyList(), Collections.emptyList(), false)); } public static EthProtocolManager create( @@ -113,7 +113,7 @@ public static EthProtocolManager create( ethPeers, ethMessages, ethContext, - new ForkIdManager(blockchain, Collections.emptyList(), false)); + new ForkIdManager(blockchain, Collections.emptyList(), Collections.emptyList(), false)); } public static EthProtocolManager create( diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/GetBodiesFromPeerTaskTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/GetBodiesFromPeerTaskTest.java index 3178afbe3cb..f86b54d5ca9 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/GetBodiesFromPeerTaskTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/GetBodiesFromPeerTaskTest.java @@ -14,17 +14,25 @@ */ package org.hyperledger.besu.ethereum.eth.manager.task; +import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.GWei; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.eth.manager.ethtaskutils.PeerMessageTaskTest; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; +import org.apache.tuweni.units.bigints.UInt64; +import org.junit.Test; + public class GetBodiesFromPeerTaskTest extends PeerMessageTaskTest> { @Override @@ -56,4 +64,21 @@ protected void assertPartialResultMatchesExpectation( assertThat(requestedData).contains(block); } } + + @Test + public void assertBodyIdentifierUsesWithdrawalsToGenerateBodyIdentifiers() { + final Withdrawal withdrawal = + new Withdrawal(UInt64.ONE, UInt64.ONE, Address.fromHexString("0x1"), GWei.ONE); + + // Empty body block + final BlockBody emptyBodyBlock = BlockBody.empty(); + // Block with no tx, no ommers, 1 withdrawal + final BlockBody bodyBlockWithWithdrawal = + new BlockBody(emptyList(), emptyList(), Optional.of(List.of(withdrawal))); + + assertThat( + new GetBodiesFromPeerTask.BodyIdentifier(emptyBodyBlock) + .equals(new GetBodiesFromPeerTask.BodyIdentifier(bodyBlockWithWithdrawal))) + .isFalse(); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/BlockBodiesMessageTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/BlockBodiesMessageTest.java index 1039171d47c..3ee837d2b0b 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/BlockBodiesMessageTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/BlockBodiesMessageTest.java @@ -18,6 +18,7 @@ import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.difficulty.fixed.FixedDifficultyProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; @@ -32,6 +33,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Optional; import com.google.common.io.Resources; import org.apache.tuweni.bytes.Bytes; @@ -60,7 +62,10 @@ public void blockBodiesRoundTrip() throws IOException { new BlockBody( oneBlock.readList(Transaction::readFrom), oneBlock.readList( - rlp -> BlockHeader.readFrom(rlp, new MainnetBlockHeaderFunctions())))); + rlp -> BlockHeader.readFrom(rlp, new MainnetBlockHeaderFunctions())), + oneBlock.isEndOfCurrentList() + ? Optional.empty() + : Optional.of(oneBlock.readList(Withdrawal::readFrom)))); } final MessageData initialMessage = BlockBodiesMessage.create(bodies); final MessageData raw = new RawMessage(EthPV62.BLOCK_BODIES, initialMessage.getData()); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/MessageWrapperTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/MessageWrapperTest.java index 2022521fbe6..cdcd1a03ee2 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/MessageWrapperTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/MessageWrapperTest.java @@ -285,7 +285,8 @@ public TestBlockBody( @JsonProperty("Uncles") final List uncles) { super( transactions.stream().collect(toUnmodifiableList()), - uncles.stream().collect(toUnmodifiableList())); + uncles.stream().collect(toUnmodifiableList()), + Optional.empty()); } } @@ -308,6 +309,7 @@ public TestBlockHeader( @JsonProperty("extraData") final String extraData, @JsonProperty("mixHash") final String mixHash, @JsonProperty("nonce") final String nonce, + @JsonProperty("withdrawalsRoot") final String withdrawalsRoot, @JsonProperty("hash") final String __) { super( Hash.fromHexString(parentHash), @@ -326,6 +328,7 @@ public TestBlockHeader( null, Hash.fromHexString(mixHash), Bytes.fromHexStringLenient(nonce).toLong(), + withdrawalsRoot == null ? Hash.EMPTY : Hash.fromHexString(withdrawalsRoot), new MainnetBlockHeaderFunctions()); } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/BlockBroadcasterTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/BlockBroadcasterTest.java index 05c036de91f..d36b6b52dc7 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/BlockBroadcasterTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/BlockBroadcasterTest.java @@ -32,6 +32,7 @@ import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection; import java.util.Collections; +import java.util.Optional; import java.util.stream.Stream; import org.junit.Test; @@ -82,7 +83,8 @@ public void blockPropagationUnitTestSeenUnseen() throws PeerConnection.PeerNotCo } private Block generateBlock() { - final BlockBody body = new BlockBody(Collections.emptyList(), Collections.emptyList()); + final BlockBody body = + new BlockBody(Collections.emptyList(), Collections.emptyList(), Optional.empty()); return new Block(new BlockHeaderTestFixture().buildHeader(), body); } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/TrailingPeerLimiterTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/TrailingPeerLimiterTest.java index 43745dba7d8..9ae83353ce9 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/TrailingPeerLimiterTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/TrailingPeerLimiterTest.java @@ -36,6 +36,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Optional; import org.junit.Before; import org.junit.Test; @@ -115,7 +116,7 @@ public void shouldRecheckTrailingPeersWhenBlockAddedThatIsMultipleOf100() { BlockAddedEvent.createForHeadAdvancement( new Block( new BlockHeaderTestFixture().number(500).buildHeader(), - new BlockBody(emptyList(), emptyList())), + new BlockBody(emptyList(), emptyList(), Optional.empty())), Collections.emptyList(), Collections.emptyList()); trailingPeerLimiter.onBlockAdded(blockAddedEvent); @@ -133,7 +134,7 @@ public void shouldNotRecheckTrailingPeersWhenBlockAddedIsNotAMultipleOf100() { BlockAddedEvent.createForHeadAdvancement( new Block( new BlockHeaderTestFixture().number(599).buildHeader(), - new BlockBody(emptyList(), emptyList())), + new BlockBody(emptyList(), emptyList(), Optional.empty())), Collections.emptyList(), Collections.emptyList()); trailingPeerLimiter.onBlockAdded(blockAddedEvent); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ChainForTestCreator.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ChainForTestCreator.java index 0b35018c599..6fa3e855fba 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ChainForTestCreator.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ChainForTestCreator.java @@ -56,6 +56,7 @@ public static BlockHeader prepareHeader(final long number, final Optional prepareChain(final int elements, final long height) { } public static Block createEmptyBlock(final Long height) { - return new Block(prepareEmptyHeader(height), new BlockBody(List.of(), List.of())); + return new Block( + prepareEmptyHeader(height), new BlockBody(List.of(), List.of(), Optional.empty())); } private static Block createEmptyBlock(final Block parent) { - return new Block(prepareEmptyHeader(parent.getHeader()), new BlockBody(List.of(), List.of())); + return new Block( + prepareEmptyHeader(parent.getHeader()), + new BlockBody(List.of(), List.of(), Optional.empty())); } private static BlockHeader prepareEmptyHeader(final Long number) { @@ -124,6 +129,7 @@ private static BlockHeader prepareEmptyHeader(final BlockHeader parent) { Wei.ZERO, Hash.EMPTY, 0, + Hash.EMPTY, new MainnetBlockHeaderFunctions()); } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckPointBlockImportStepTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckPointBlockImportStepTest.java index e57a8f87a29..10a04dadd9f 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckPointBlockImportStepTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckPointBlockImportStepTest.java @@ -76,7 +76,8 @@ public void shouldSaveChainHeadForLastBlock() { } private Block generateBlock(final int blockNumber) { - final BlockBody body = new BlockBody(Collections.emptyList(), Collections.emptyList()); + final BlockBody body = + new BlockBody(Collections.emptyList(), Collections.emptyList(), Optional.empty()); return new Block(new BlockHeaderTestFixture().number(blockNumber).buildHeader(), body); } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldDownloadStateTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldDownloadStateTest.java index be7e8dafe08..eb090743fc2 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldDownloadStateTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldDownloadStateTest.java @@ -329,7 +329,7 @@ public void shouldStopWaitingBlockchainWhenNewPivotBlockAvailable() { BlockAddedEvent.createForHeadAdvancement( new Block( new BlockHeaderTestFixture().number(500).buildHeader(), - new BlockBody(emptyList(), emptyList())), + new BlockBody(emptyList(), emptyList(), Optional.empty())), Collections.emptyList(), Collections.emptyList())); @@ -355,7 +355,7 @@ public void shouldStopWaitingBlockchainWhenCloseToTheHead() { BlockAddedEvent.createForHeadAdvancement( new Block( new BlockHeaderTestFixture().number(500).buildHeader(), - new BlockBody(emptyList(), emptyList())), + new BlockBody(emptyList(), emptyList(), Optional.empty())), Collections.emptyList(), Collections.emptyList())); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TestNode.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TestNode.java index 11c005f06fa..22a6840f413 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TestNode.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TestNode.java @@ -180,7 +180,8 @@ public TestNode( .supportedCapabilities(capabilities) .storageProvider(new InMemoryKeyValueStorageProvider()) .blockchain(blockchain) - .forks(Collections.emptyList()) + .blockNumberForks(Collections.emptyList()) + .timestampForks(Collections.emptyList()) .build()) .metricsSystem(new NoOpMetricsSystem()) .build(); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolLegacyTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolLegacyTest.java index f43a377f499..cd5fb4ab407 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolLegacyTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolLegacyTest.java @@ -111,7 +111,7 @@ protected Block appendBlock( .parentHash(parentBlock.getHash()) .number(parentBlock.getNumber() + 1) .buildHeader(), - new BlockBody(transactionList, emptyList())); + new BlockBody(transactionList, emptyList(), Optional.empty())); final List transactionReceipts = transactionList.stream() .map(transaction -> new TransactionReceipt(1, 1, emptyList(), Optional.empty())) diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolLondonTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolLondonTest.java index 0d27b750812..aba8a43a6bf 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolLondonTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolLondonTest.java @@ -126,7 +126,7 @@ protected ExecutionContextTestFixture createExecutionContextTestFixture() { .parentHash(executionContextTestFixture.getBlockchain().getChainHeadHash()) .number(executionContextTestFixture.getBlockchain().getChainHeadBlockNumber() + 1) .buildHeader(), - new BlockBody(List.of(), List.of())); + new BlockBody(List.of(), List.of(), Optional.empty())); executionContextTestFixture.getBlockchain().appendBlock(block, List.of()); return executionContextTestFixture; @@ -152,7 +152,7 @@ protected Block appendBlock( .parentHash(parentBlock.getHash()) .number(parentBlock.getNumber() + 1) .buildHeader(), - new BlockBody(transactionList, emptyList())); + new BlockBody(transactionList, emptyList(), Optional.empty())); final List transactionReceipts = transactionList.stream() .map(transaction -> new TransactionReceipt(1, 1, emptyList(), Optional.empty())) 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 b5b6f553ff9..613518f4496 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 @@ -480,7 +480,8 @@ public static class Builder { private StorageProvider storageProvider; private Optional p2pTLSConfiguration = Optional.empty(); private Blockchain blockchain; - private List forks; + private List blockNumberForks; + private List timestampForks; private boolean legacyForkIdEnabled = false; public P2PNetwork build() { @@ -523,11 +524,13 @@ private void validate() { checkState(metricsSystem != null, "MetricsSystem must be set."); checkState(storageProvider != null, "StorageProvider must be set."); checkState(peerDiscoveryAgent != null || vertx != null, "Vertx must be set."); + checkState(blockNumberForks != null, "BlockNumberForks must be set."); + checkState(timestampForks != null, "TimestampForks must be set."); } private PeerDiscoveryAgent createDiscoveryAgent() { final ForkIdManager forkIdManager = - new ForkIdManager(blockchain, forks, this.legacyForkIdEnabled); + new ForkIdManager(blockchain, blockNumberForks, timestampForks, this.legacyForkIdEnabled); return new VertxPeerDiscoveryAgent( vertx, @@ -643,9 +646,15 @@ public Builder blockchain(final MutableBlockchain blockchain) { return this; } - public Builder forks(final List forks) { + public Builder blockNumberForks(final List forks) { checkNotNull(forks); - this.forks = forks; + this.blockNumberForks = forks; + return this; + } + + public Builder timestampForks(final List forks) { + checkNotNull(forks); + this.timestampForks = forks; return this; } diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetworkTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetworkTest.java index ebd92641353..d0c865b34d7 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetworkTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetworkTest.java @@ -52,6 +52,7 @@ import org.hyperledger.besu.plugin.data.EnodeURL; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -407,6 +408,8 @@ private DefaultP2PNetwork.Builder builder() { .maintainedPeers(maintainedPeers) .metricsSystem(new NoOpMetricsSystem()) .supportedCapabilities(Capability.create("eth", 63)) - .storageProvider(new InMemoryKeyValueStorageProvider()); + .storageProvider(new InMemoryKeyValueStorageProvider()) + .blockNumberForks(Collections.emptyList()) + .timestampForks(Collections.emptyList()); } } 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 cb4aacfc3f4..e17ec170beb 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 @@ -82,7 +82,8 @@ private DefaultP2PNetwork.Builder getP2PNetworkBuilder() { when(blockMock.getHash()).thenReturn(Hash.ZERO); when(blockchainMock.getGenesisBlock()).thenReturn(blockMock); builder.blockchain(blockchainMock); - builder.forks(Collections.emptyList()); + builder.blockNumberForks(Collections.emptyList()); + builder.timestampForks(Collections.emptyList()); return builder; } diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/P2PNetworkTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/P2PNetworkTest.java index a528be6d13a..7ed9038ee31 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/P2PNetworkTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/P2PNetworkTest.java @@ -321,7 +321,8 @@ private DefaultP2PNetwork.Builder builder() { .metricsSystem(new NoOpMetricsSystem()) .supportedCapabilities(Arrays.asList(Capability.create("eth", 63))) .storageProvider(new InMemoryKeyValueStorageProvider()) - .forks(Collections.emptyList()) + .blockNumberForks(Collections.emptyList()) + .timestampForks(Collections.emptyList()) .blockchain(blockchainMock); } } diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/P2PPlainNetworkTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/P2PPlainNetworkTest.java index bb6ff961b9d..ad25f94eb2b 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/P2PPlainNetworkTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/P2PPlainNetworkTest.java @@ -504,7 +504,8 @@ private DefaultP2PNetwork.Builder builder(final String name) { .metricsSystem(new NoOpMetricsSystem()) .supportedCapabilities(Arrays.asList(Capability.create("eth", 63))) .storageProvider(new InMemoryKeyValueStorageProvider()) - .forks(Collections.emptyList()) + .blockNumberForks(Collections.emptyList()) + .timestampForks(Collections.emptyList()) .blockchain(blockchainMock); } } diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BlockchainReferenceTestCaseSpec.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BlockchainReferenceTestCaseSpec.java index 34a8564c6de..9d613228179 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BlockchainReferenceTestCaseSpec.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BlockchainReferenceTestCaseSpec.java @@ -31,6 +31,7 @@ import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.ParsedExtraData; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; import org.hyperledger.besu.ethereum.rlp.RLPInput; @@ -39,6 +40,7 @@ import org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.util.Map; +import java.util.Optional; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -157,6 +159,7 @@ public ReferenceTestBlockHeader( @JsonProperty("baseFeePerGas") final String baseFee, @JsonProperty("mixHash") final String mixHash, @JsonProperty("nonce") final String nonce, + @JsonProperty("withdrawalsRoot") final String withdrawalsRoot, @JsonProperty("hash") final String hash) { super( Hash.fromHexString(parentHash), // parentHash @@ -175,6 +178,7 @@ public ReferenceTestBlockHeader( baseFee != null ? Wei.fromHexString(baseFee) : null, // baseFee Hash.fromHexString(mixHash), // mixHash Bytes.fromHexStringLenient(nonce).toLong(), + withdrawalsRoot == null ? Hash.EMPTY : Hash.fromHexString(withdrawalsRoot), new BlockHeaderFunctions() { @Override public Hash hash(final BlockHeader header) { @@ -250,7 +254,10 @@ public Block getBlock() { final BlockBody body = new BlockBody( input.readList(Transaction::readFrom), - input.readList(inputData -> BlockHeader.readFrom(inputData, blockHeaderFunctions))); + input.readList(inputData -> BlockHeader.readFrom(inputData, blockHeaderFunctions)), + input.isEndOfCurrentList() + ? Optional.empty() + : Optional.of(input.readList(Withdrawal::readFrom))); return new Block(header, body); } } diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestEnv.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestEnv.java index 390957fd819..2c2cdabfd59 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestEnv.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestEnv.java @@ -74,6 +74,7 @@ public ReferenceTestEnv( Optional.ofNullable(baseFee).map(Wei::fromHexString).orElse(null), Optional.ofNullable(random).map(Difficulty::fromHexString).orElse(Difficulty.ZERO), 0L, + Hash.EMPTY, new MainnetBlockHeaderFunctions()); } 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 348cb179fae..eacaa5da0cc 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 @@ -73,9 +73,8 @@ public static ReferenceTestProtocolSchedules create() { builder.put( "Merge", createSchedule(new StubGenesisConfigOptions().mergeNetSplitBlock(0).baseFeePerGas(0x0a))); - builder.put( - "Shanghai", - createSchedule(new StubGenesisConfigOptions().shandongBlock(0).baseFeePerGas(0x0a))); + builder.put("Shanghai", createSchedule(new StubGenesisConfigOptions().shanghaiTime(0))); + builder.put("Cancun", createSchedule(new StubGenesisConfigOptions().cancunTime(0))); builder.put("Shandong", createSchedule(new StubGenesisConfigOptions().shandongBlock(0))); return new ReferenceTestProtocolSchedules(builder.build()); } diff --git a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/NoRewardProtocolScheduleWrapper.java b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/NoRewardProtocolScheduleWrapper.java index bd1be09ac88..5e84c93fa86 100644 --- a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/NoRewardProtocolScheduleWrapper.java +++ b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/NoRewardProtocolScheduleWrapper.java @@ -79,7 +79,9 @@ public ProtocolSpec getByBlockNumber(final long number) { original.getGasLimitCalculator(), original.getFeeMarket(), original.getBadBlocksManager(), - Optional.empty()); + Optional.empty(), + original.getWithdrawalsProcessor(), + original.getWithdrawalsValidator()); } @Override @@ -92,6 +94,16 @@ public Optional getChainId() { return delegate.getChainId(); } + @Override + public void putMilestone(final long blockOrTimestamp, final ProtocolSpec protocolSpec) { + delegate.putMilestone(blockOrTimestamp, protocolSpec); + } + + @Override + public String listMilestones() { + return delegate.listMilestones(); + } + @Override public void setTransactionFilter(final TransactionFilter transactionFilter) { delegate.setTransactionFilter(transactionFilter); diff --git a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestSetChainParams.java b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestSetChainParams.java index c4064b1cd4b..860c36263da 100644 --- a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestSetChainParams.java +++ b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestSetChainParams.java @@ -126,6 +126,8 @@ private static String modifyGenesisFile(final String initialGenesis) { maybeMoveToNumber(params, "arrowGlacierForkBlock", config, "arrowGlacierBlock"); maybeMoveToNumber(params, "grayGlacierForkBlock", config, "grayGlacierBlock"); maybeMoveToNumber(params, "mergeNetSplitForkBlock", config, "mergeNetSplitBlock"); + maybeMoveToNumber(params, "shanghaiForkTime", config, "shanghaiTime"); + maybeMoveToNumber(params, "cancunForkTime", config, "cancunTime"); maybeMoveToNumber(params, "shandongForkBlock", config, "shandongBlock"); maybeMoveToNumber(params, "chainID", config, "chainId", 1); diff --git a/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/AbstractRLPInput.java b/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/AbstractRLPInput.java index 65c07297fae..942a0d44e44 100644 --- a/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/AbstractRLPInput.java +++ b/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/AbstractRLPInput.java @@ -27,6 +27,7 @@ import org.apache.tuweni.bytes.MutableBytes; import org.apache.tuweni.bytes.MutableBytes32; import org.apache.tuweni.units.bigints.UInt256; +import org.apache.tuweni.units.bigints.UInt64; abstract class AbstractRLPInput implements RLPInput { @@ -324,6 +325,19 @@ public BigInteger readBigIntegerScalar() { return res; } + private Bytes readBytes8Scalar() { + checkScalar("8-bytes scalar", 8); + final MutableBytes res = MutableBytes.create(8); + payloadSlice().copyTo(res, res.size() - currentPayloadSize); + setTo(nextItem()); + return res; + } + + @Override + public UInt64 readUInt64Scalar() { + return UInt64.fromBytes(readBytes8Scalar()); + } + private Bytes32 readBytes32Scalar() { checkScalar("32-bytes scalar", 32); final MutableBytes32 res = MutableBytes32.create(); diff --git a/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPInput.java b/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPInput.java index 49b01c65404..184e17219a7 100644 --- a/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPInput.java +++ b/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPInput.java @@ -23,6 +23,7 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; +import org.apache.tuweni.units.bigints.UInt64; /** * An input used to decode data in RLP encoding. @@ -165,6 +166,16 @@ public interface RLPInput { */ BigInteger readBigIntegerScalar(); + /** + * Reads a scalar from the input and return is as a {@link UInt64}. + * + * @return The next scalar item of this input as a {@link UInt64}. + * @throws RLPException if the next item to read is a list, the input is at the end of its current + * list (and {@link #leaveList()} hasn't been called) or if the next item is either too big to + * fit a {@link UInt64} or has leading zeros. + */ + UInt64 readUInt64Scalar(); + /** * Reads a scalar from the input and return is as a {@link UInt256}. * diff --git a/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPOutput.java b/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPOutput.java index a54cfc7d6e6..140e60d9d24 100644 --- a/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPOutput.java +++ b/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPOutput.java @@ -22,6 +22,7 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.MutableBytes; import org.apache.tuweni.units.bigints.UInt256Value; +import org.apache.tuweni.units.bigints.UInt64Value; /** * An output used to encode data in RLP encoding. @@ -71,6 +72,14 @@ public interface RLPOutput { */ void writeBytes(Bytes v); + /** + * Writes a scalar 64 bits unsigned int (encoded without leading zeros) + * + * @param v unsigned 64 bit integer value + */ + default void writeUInt64Scalar(final UInt64Value v) { + writeBytes(v.toBytes().trimLeadingZeros()); + } /** * Writes a scalar (encoded with no leading zeroes). * diff --git a/evm/src/main/java/org/hyperledger/besu/evm/EvmSpecVersion.java b/evm/src/main/java/org/hyperledger/besu/evm/EvmSpecVersion.java index 12ff84b5e79..bfd6766be61 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/EvmSpecVersion.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/EvmSpecVersion.java @@ -26,7 +26,7 @@ public enum EvmSpecVersion { ISTANBUL(0, true), LONDON(0, true), PARIS(0, true), - SHANGHAI(1, false), + SHANGHAI(0, false), /** Transient fork, will be removed */ SHANDONG(1, false); 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 051ec5fda7c..86972fd7f00 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java @@ -117,6 +117,9 @@ public class MainnetEVMs { public static final BigInteger DEV_NET_CHAIN_ID = BigInteger.valueOf(1337); + public static final int SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT = 0x6000; + public static final int SHANGHAI_INIT_CODE_SIZE_LIMIT = 2 * SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT; + private MainnetEVMs() { // utility class } @@ -203,7 +206,7 @@ public static void registerFrontierOperations( registry.put(new InvalidOperation(gasCalculator)); registry.put(new StopOperation(gasCalculator)); registry.put(new SelfDestructOperation(gasCalculator)); - registry.put(new CreateOperation(gasCalculator)); + registry.put(new CreateOperation(gasCalculator, Integer.MAX_VALUE)); registry.put(new CallOperation(gasCalculator)); registry.put(new CallCodeOperation(gasCalculator)); @@ -311,7 +314,7 @@ public static OperationRegistry constantinopleOperations(final GasCalculator gas public static void registerConstantinopleOperations( final OperationRegistry registry, final GasCalculator gasCalculator) { registerByzantiumOperations(registry, gasCalculator); - registry.put(new Create2Operation(gasCalculator)); + registry.put(new Create2Operation(gasCalculator, Integer.MAX_VALUE)); registry.put(new SarOperation(gasCalculator)); registry.put(new ShlOperation(gasCalculator)); registry.put(new ShrOperation(gasCalculator)); @@ -427,6 +430,39 @@ public static void registerParisOperations( registry.put(new PrevRanDaoOperation(gasCalculator)); } + public static EVM shanghai(final BigInteger chainId, final EvmConfiguration evmConfiguration) { + return shanghai(new LondonGasCalculator(), chainId, evmConfiguration); + } + + public static EVM shanghai( + final GasCalculator gasCalculator, + final BigInteger chainId, + final EvmConfiguration evmConfiguration) { + return new EVM( + shanghaiOperations(gasCalculator, chainId), + gasCalculator, + evmConfiguration, + EvmSpecVersion.SHANGHAI); + } + + public static OperationRegistry shanghaiOperations( + final GasCalculator gasCalculator, final BigInteger chainId) { + OperationRegistry operationRegistry = new OperationRegistry(); + registerShanghaiOperations(operationRegistry, gasCalculator, chainId); + return operationRegistry; + } + + public static void registerShanghaiOperations( + final OperationRegistry registry, + final GasCalculator gasCalculator, + final BigInteger chainID) { + registerParisOperations(registry, gasCalculator, chainID); + // Register the PUSH0 operation. + registry.put(new Push0Operation(gasCalculator)); + registry.put(new CreateOperation(gasCalculator, SHANGHAI_INIT_CODE_SIZE_LIMIT)); + registry.put(new Create2Operation(gasCalculator, SHANGHAI_INIT_CODE_SIZE_LIMIT)); + } + public static EVM shandong(final BigInteger chainId, final EvmConfiguration evmConfiguration) { return shandong(new LondonGasCalculator(), chainId, evmConfiguration); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/ShanghaiGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/ShanghaiGasCalculator.java new file mode 100644 index 00000000000..d313d54b759 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/ShanghaiGasCalculator.java @@ -0,0 +1,48 @@ +package org.hyperledger.besu.evm.gascalculator; +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import static org.hyperledger.besu.evm.internal.Words.clampedAdd; +import static org.hyperledger.besu.evm.internal.Words.clampedToLong; + +import org.hyperledger.besu.evm.frame.MessageFrame; + +import org.apache.tuweni.bytes.Bytes; + +public class ShanghaiGasCalculator extends LondonGasCalculator { + + private static final long INIT_CODE_COST = 2L; + + @Override + public long transactionIntrinsicGasCost(final Bytes payload, final boolean isContractCreation) { + long intrinsicGasCost = super.transactionIntrinsicGasCost(payload, isContractCreation); + if (isContractCreation) { + return clampedAdd(intrinsicGasCost, calculateInitGasCost(payload.size())); + } else { + return intrinsicGasCost; + } + } + + @Override + public long createOperationGasCost(final MessageFrame frame) { + final long initCodeLength = clampedToLong(frame.getStackItem(2)); + return clampedAdd(super.createOperationGasCost(frame), calculateInitGasCost(initCodeLength)); + } + + private static long calculateInitGasCost(final long initCodeLength) { + final int dataLength = (int) Math.ceil(initCodeLength / 32.0); + return dataLength * INIT_CODE_COST; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java index 3ac8af46701..77fec559aad 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java @@ -36,14 +36,17 @@ public abstract class AbstractCreateOperation extends AbstractOperation { protected static final OperationResult UNDERFLOW_RESPONSE = new OperationResult(0L, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + protected int maxInitcodeSize; + protected AbstractCreateOperation( final int opcode, final String name, final int stackItemsConsumed, final int stackItemsProduced, - final int opSize, - final GasCalculator gasCalculator) { - super(opcode, name, stackItemsConsumed, stackItemsProduced, opSize, gasCalculator); + final GasCalculator gasCalculator, + final int maxInitcodeSize) { + super(opcode, name, stackItemsConsumed, stackItemsProduced, maxInitcodeSize, gasCalculator); + this.maxInitcodeSize = maxInitcodeSize; } @Override @@ -75,6 +78,10 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { final long inputOffset = clampedToLong(frame.getStackItem(1)); final long inputSize = clampedToLong(frame.getStackItem(2)); + if (inputSize > maxInitcodeSize) { + frame.popStackItems(getStackItemsConsumed()); + return new OperationResult(cost, ExceptionalHaltReason.CODE_TOO_LARGE); + } final Bytes inputData = frame.readMemory(inputOffset, inputSize); Code code = evm.getCode(Hash.hash(inputData), inputData); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/Create2Operation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/Create2Operation.java index 8e7d823f525..f32359adc5f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/Create2Operation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/Create2Operation.java @@ -29,8 +29,8 @@ public class Create2Operation extends AbstractCreateOperation { private static final Bytes PREFIX = Bytes.fromHexString("0xFF"); - public Create2Operation(final GasCalculator gasCalculator) { - super(0xF5, "CREATE2", 4, 1, 1, gasCalculator); + public Create2Operation(final GasCalculator gasCalculator, final int maxInitcodeSize) { + super(0xF5, "CREATE2", 4, 1, gasCalculator, maxInitcodeSize); } @Override diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CreateOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CreateOperation.java index 4f58c2cd591..2cf784cdfa5 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CreateOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CreateOperation.java @@ -21,8 +21,8 @@ public class CreateOperation extends AbstractCreateOperation { - public CreateOperation(final GasCalculator gasCalculator) { - super(0xF0, "CREATE", 3, 1, 1, gasCalculator); + public CreateOperation(final GasCalculator gasCalculator, final int maxInitcodeSize) { + super(0xF0, "CREATE", 3, 1, gasCalculator, maxInitcodeSize); } @Override diff --git a/plugin-api/build.gradle b/plugin-api/build.gradle index 648fd64349d..a4af5a6de6d 100644 --- a/plugin-api/build.gradle +++ b/plugin-api/build.gradle @@ -66,7 +66,7 @@ Calculated : ${currentHash} tasks.register('checkAPIChanges', FileStateChecker) { description = "Checks that the API for the Plugin-API project does not change without deliberate thought" files = sourceSets.main.allJava.files - knownHash = 'nBDCEeFH318uhGZEBmuTGOfYLI1+9tLDyjn/RDe5saI=' + knownHash = '+Oy6rVYnpvuhHiMo16Mfr+A2jkQ+7T1/dsLxZVdet84=' } check.dependsOn('checkAPIChanges') diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockHeader.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockHeader.java index 6e7e39aabfc..1815597b985 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockHeader.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockHeader.java @@ -178,4 +178,6 @@ default Optional getBaseFee() { default Optional getPrevRandao() { return Optional.empty(); } + + Hash getWithdrawalRoot(); }