diff --git a/CHANGELOG.md b/CHANGELOG.md index c241838637..ff4d7cfbf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,10 @@ - Remove privacy test classes support [#7569](https://github.com/hyperledger/besu/pull/7569) ### Bug fixes +- Fix mounted data path directory permissions for besu user [#7575](https://github.com/hyperledger/besu/pull/7575) - Fix for `debug_traceCall` to handle transactions without specified gas price. [#7510](https://github.com/hyperledger/besu/pull/7510) +- Corrects a regression where custom plugin services are not initialized correctly. [#7625](https://github.com/hyperledger/besu/pull/7625) + ## 24.9.1 diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java index c6696b15a2..6e00701ef2 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java @@ -75,6 +75,70 @@ public class ProcessBesuNodeRunner implements BesuNodeRunner { final Path dataDir = node.homeDirectory(); + final List params = commandlineArgs(node, dataDir); + + LOG.info("Creating besu process with params {}", params); + final ProcessBuilder processBuilder = + new ProcessBuilder(params) + .directory(new File(System.getProperty("user.dir")).getParentFile().getParentFile()) + .redirectErrorStream(true) + .redirectInput(Redirect.INHERIT); + if (!node.getPlugins().isEmpty()) { + processBuilder + .environment() + .put( + "BESU_OPTS", + "-Dbesu.plugins.dir=" + dataDir.resolve("plugins").toAbsolutePath().toString()); + } + // Use non-blocking randomness for acceptance tests + processBuilder + .environment() + .put( + "JAVA_OPTS", + "-Djava.security.properties=" + + "acceptance-tests/tests/build/resources/test/acceptanceTesting.security"); + // add additional environment variables + processBuilder.environment().putAll(node.getEnvironment()); + + try { + int debugPort = Integer.parseInt(System.getenv("BESU_DEBUG_CHILD_PROCESS_PORT")); + LOG.warn("Waiting for debugger to attach to SUSPENDED child process"); + String debugOpts = + " -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:" + debugPort; + String prevJavaOpts = processBuilder.environment().get("JAVA_OPTS"); + if (prevJavaOpts == null) { + processBuilder.environment().put("JAVA_OPTS", debugOpts); + } else { + processBuilder.environment().put("JAVA_OPTS", prevJavaOpts + debugOpts); + } + + } catch (NumberFormatException e) { + LOG.debug( + "Child process may be attached to by exporting BESU_DEBUG_CHILD_PROCESS_PORT= to env"); + } + + try { + checkState( + isNotAliveOrphan(node.getName()), + "A live process with name: %s, already exists. Cannot create another with the same name as it would orphan the first", + node.getName()); + + final Process process = processBuilder.start(); + process.onExit().thenRun(() -> node.setExitCode(process.exitValue())); + outputProcessorExecutor.execute(() -> printOutput(node, process)); + besuProcesses.put(node.getName(), process); + } catch (final IOException e) { + LOG.error("Error starting BesuNode process", e); + } + + if (node.getRunCommand().isEmpty()) { + waitForFile(dataDir, "besu.ports"); + waitForFile(dataDir, "besu.networks"); + } + MDC.remove("node"); + } + + private List commandlineArgs(final BesuNode node, final Path dataDir) { final List params = new ArrayList<>(); params.add("build/install/besu/bin/besu"); @@ -388,66 +452,7 @@ public class ProcessBesuNodeRunner implements BesuNodeRunner { } params.addAll(node.getRunCommand()); - - LOG.info("Creating besu process with params {}", params); - final ProcessBuilder processBuilder = - new ProcessBuilder(params) - .directory(new File(System.getProperty("user.dir")).getParentFile().getParentFile()) - .redirectErrorStream(true) - .redirectInput(Redirect.INHERIT); - if (!node.getPlugins().isEmpty()) { - processBuilder - .environment() - .put( - "BESU_OPTS", - "-Dbesu.plugins.dir=" + dataDir.resolve("plugins").toAbsolutePath().toString()); - } - // Use non-blocking randomness for acceptance tests - processBuilder - .environment() - .put( - "JAVA_OPTS", - "-Djava.security.properties=" - + "acceptance-tests/tests/build/resources/test/acceptanceTesting.security"); - // add additional environment variables - processBuilder.environment().putAll(node.getEnvironment()); - - try { - int debugPort = Integer.parseInt(System.getenv("BESU_DEBUG_CHILD_PROCESS_PORT")); - LOG.warn("Waiting for debugger to attach to SUSPENDED child process"); - String debugOpts = - " -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:" + debugPort; - String prevJavaOpts = processBuilder.environment().get("JAVA_OPTS"); - if (prevJavaOpts == null) { - processBuilder.environment().put("JAVA_OPTS", debugOpts); - } else { - processBuilder.environment().put("JAVA_OPTS", prevJavaOpts + debugOpts); - } - - } catch (NumberFormatException e) { - LOG.debug( - "Child process may be attached to by exporting BESU_DEBUG_CHILD_PROCESS_PORT= to env"); - } - - try { - checkState( - isNotAliveOrphan(node.getName()), - "A live process with name: %s, already exists. Cannot create another with the same name as it would orphan the first", - node.getName()); - - final Process process = processBuilder.start(); - process.onExit().thenRun(() -> node.setExitCode(process.exitValue())); - outputProcessorExecutor.execute(() -> printOutput(node, process)); - besuProcesses.put(node.getName(), process); - } catch (final IOException e) { - LOG.error("Error starting BesuNode process", e); - } - - if (node.getRunCommand().isEmpty()) { - waitForFile(dataDir, "besu.ports"); - waitForFile(dataDir, "besu.networks"); - } - MDC.remove("node"); + return params; } private boolean isNotAliveOrphan(final String name) { diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java index d4ec54045d..5d78f1460c 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java @@ -138,7 +138,8 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner { final PermissioningServiceImpl permissioningService = new PermissioningServiceImpl(); GlobalOpenTelemetry.resetForTest(); - final ObservableMetricsSystem metricsSystem = component.getObservableMetricsSystem(); + final ObservableMetricsSystem metricsSystem = + (ObservableMetricsSystem) component.getMetricsSystem(); final List bootnodes = node.getConfiguration().getBootnodes().stream().map(EnodeURLImpl::fromURI).toList(); @@ -280,6 +281,16 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner { this.toProvide = toProvide; } + @Provides + @Singleton + MetricsConfiguration provideMetricsConfiguration() { + if (toProvide.getMetricsConfiguration() != null) { + return toProvide.getMetricsConfiguration(); + } else { + return MetricsConfiguration.builder().build(); + } + } + @Provides public BesuNode provideBesuNodeRunner() { return toProvide; @@ -410,13 +421,13 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner { public BesuController provideBesuController( final SynchronizerConfiguration synchronizerConfiguration, final BesuControllerBuilder builder, - final ObservableMetricsSystem metricsSystem, + final MetricsSystem metricsSystem, final KeyValueStorageProvider storageProvider, final MiningParameters miningParameters) { builder .synchronizerConfiguration(synchronizerConfiguration) - .metricsSystem(metricsSystem) + .metricsSystem((ObservableMetricsSystem) metricsSystem) .dataStorageConfiguration(DataStorageConfiguration.DEFAULT_FOREST_CONFIG) .ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig()) .clock(Clock.systemUTC()) @@ -562,12 +573,6 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner { return besuCommand; } - @Provides - @Singleton - MetricsConfiguration provideMetricsConfiguration() { - return MetricsConfiguration.builder().build(); - } - @Provides @Named("besuCommandLogger") @Singleton diff --git a/besu/src/main/java/org/hyperledger/besu/Besu.java b/besu/src/main/java/org/hyperledger/besu/Besu.java index 8d40a10eb4..3ba0af0a9b 100644 --- a/besu/src/main/java/org/hyperledger/besu/Besu.java +++ b/besu/src/main/java/org/hyperledger/besu/Besu.java @@ -16,6 +16,7 @@ package org.hyperledger.besu; import org.hyperledger.besu.cli.BesuCommand; import org.hyperledger.besu.cli.logging.BesuLoggingConfigurationFactory; +import org.hyperledger.besu.components.BesuComponent; import org.hyperledger.besu.components.DaggerBesuComponent; import io.netty.util.internal.logging.InternalLoggerFactory; @@ -36,13 +37,15 @@ public final class Besu { */ public static void main(final String... args) { setupLogging(); - final BesuCommand besuCommand = DaggerBesuComponent.create().getBesuCommand(); + final BesuComponent besuComponent = DaggerBesuComponent.create(); + final BesuCommand besuCommand = besuComponent.getBesuCommand(); int exitCode = besuCommand.parse( new RunLast(), besuCommand.parameterExceptionHandler(), besuCommand.executionExceptionHandler(), System.in, + besuComponent, args); System.exit(exitCode); diff --git a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java index ecfc0eaadb..79879538bd 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -16,6 +16,7 @@ package org.hyperledger.besu.cli; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.hyperledger.besu.cli.DefaultCommandValues.getDefaultBesuDataPath; @@ -84,6 +85,7 @@ import org.hyperledger.besu.cli.util.BesuCommandCustomFactory; import org.hyperledger.besu.cli.util.CommandLineUtils; import org.hyperledger.besu.cli.util.ConfigDefaultValueProviderStrategy; import org.hyperledger.besu.cli.util.VersionProvider; +import org.hyperledger.besu.components.BesuComponent; import org.hyperledger.besu.config.CheckpointConfigOptions; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.config.GenesisConfigOptions; @@ -144,7 +146,6 @@ import org.hyperledger.besu.evm.precompile.KZGPointEvalPrecompiledContract; import org.hyperledger.besu.metrics.BesuMetricCategory; import org.hyperledger.besu.metrics.MetricCategoryRegistryImpl; import org.hyperledger.besu.metrics.MetricsProtocol; -import org.hyperledger.besu.metrics.MetricsSystemFactory; import org.hyperledger.besu.metrics.ObservableMetricsSystem; import org.hyperledger.besu.metrics.StandardMetricCategory; import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; @@ -203,16 +204,23 @@ import org.hyperledger.besu.util.number.Fraction; import org.hyperledger.besu.util.number.Percentage; import org.hyperledger.besu.util.number.PositiveNumber; +import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.math.BigInteger; import java.net.InetAddress; import java.net.SocketException; import java.net.URI; import java.net.URL; import java.net.UnknownHostException; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.attribute.GroupPrincipal; +import java.nio.file.attribute.PosixFileAttributes; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.UserPrincipal; import java.time.Clock; import java.util.ArrayList; import java.util.Arrays; @@ -232,6 +240,7 @@ import java.util.function.Supplier; import java.util.stream.Collectors; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Splitter; import com.google.common.base.Strings; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableMap; @@ -243,6 +252,8 @@ import org.apache.commons.net.util.SubnetUtils.SubnetInfo; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; import org.slf4j.Logger; +import oshi.PlatformEnum; +import oshi.SystemInfo; import picocli.AutoComplete; import picocli.CommandLine; import picocli.CommandLine.Command; @@ -382,6 +393,28 @@ public class BesuCommand implements DefaultCommandValues, Runnable { arity = "1") private final Optional identityString = Optional.empty(); + private Boolean printPathsAndExit = Boolean.FALSE; + private String besuUserName = "besu"; + + @Option( + names = "--print-paths-and-exit", + paramLabel = "", + description = "Print the configured paths and exit without starting the node.", + arity = "0..1") + void setUserName(final String userName) { + PlatformEnum currentPlatform = SystemInfo.getCurrentPlatform(); + // Only allow on Linux and macOS + if (currentPlatform == PlatformEnum.LINUX || currentPlatform == PlatformEnum.MACOS) { + if (userName != null) { + besuUserName = userName; + } + printPathsAndExit = Boolean.TRUE; + } else { + throw new UnsupportedOperationException( + "--print-paths-and-exit is only supported on Linux and macOS."); + } + } + // P2P Discovery Option Group @CommandLine.ArgGroup(validate = false, heading = "@|bold P2P Discovery Options|@%n") P2PDiscoveryOptionGroup p2PDiscoveryOptionGroup = new P2PDiscoveryOptionGroup(); @@ -390,6 +423,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { private final TransactionPoolValidatorServiceImpl transactionValidatorServiceImpl; private final TransactionSimulationServiceImpl transactionSimulationServiceImpl; private final BlockchainServiceImpl blockchainServiceImpl; + private BesuComponent besuComponent; static class P2PDiscoveryOptionGroup { @@ -864,9 +898,6 @@ public class BesuCommand implements DefaultCommandValues, Runnable { private BesuController besuController; private BesuConfigurationImpl pluginCommonConfiguration; - private final Supplier metricsSystem = - Suppliers.memoize(() -> MetricsSystemFactory.create(metricsConfiguration())); - private Vertx vertx; private EnodeDnsConfiguration enodeDnsConfiguration; private KeyValueStorageProvider keyValueStorageProvider; @@ -996,6 +1027,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { * @param parameterExceptionHandler Handler for exceptions related to command line parameters. * @param executionExceptionHandler Handler for exceptions during command execution. * @param in The input stream for commands. + * @param besuComponent The Besu component. * @param args The command line arguments. * @return The execution result status code. */ @@ -1004,8 +1036,12 @@ public class BesuCommand implements DefaultCommandValues, Runnable { final BesuParameterExceptionHandler parameterExceptionHandler, final BesuExecutionExceptionHandler executionExceptionHandler, final InputStream in, + final BesuComponent besuComponent, final String... args) { - + if (besuComponent == null) { + throw new IllegalArgumentException("BesuComponent must be provided"); + } + this.besuComponent = besuComponent; initializeCommandLineSettings(in); // Create the execution strategy chain. @@ -1093,6 +1129,12 @@ public class BesuCommand implements DefaultCommandValues, Runnable { try { configureLogging(true); + if (printPathsAndExit) { + // Print configured paths requiring read/write permissions to be adjusted + checkPermissionsAndPrintPaths(besuUserName); + System.exit(0); // Exit before any services are started + } + // set merge config on the basis of genesis config setMergeConfigOptions(); @@ -1103,7 +1145,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { logger.info("Starting Besu"); // Need to create vertx after cmdline has been parsed, such that metricsSystem is configurable - vertx = createVertx(createVertxOptions(metricsSystem.get())); + vertx = createVertx(createVertxOptions(besuComponent.getMetricsSystem())); validateOptions(); @@ -1138,6 +1180,104 @@ public class BesuCommand implements DefaultCommandValues, Runnable { } } + private void checkPermissionsAndPrintPaths(final String userName) { + // Check permissions for the data path + checkPermissions(dataDir(), userName, false); + + // Check permissions for genesis file + try { + if (genesisFile != null) { + checkPermissions(genesisFile.toPath(), userName, true); + } + } catch (Exception e) { + commandLine + .getOut() + .println("Error: Failed checking genesis file: Reason: " + e.getMessage()); + } + } + + // Helper method to check permissions on a given path + private void checkPermissions(final Path path, final String besuUser, final boolean readOnly) { + try { + // Get the permissions of the file + // check if besu user is the owner - get owner permissions if yes + // else, check if besu user and owner are in the same group - if yes, check the group + // permission + // otherwise check permissions for others + + // Get the owner of the file or directory + UserPrincipal owner = Files.getOwner(path); + boolean hasReadPermission, hasWritePermission; + + // Get file permissions + Set permissions = Files.getPosixFilePermissions(path); + + // Check if besu is the owner + if (owner.getName().equals(besuUser)) { + // Owner permissions + hasReadPermission = permissions.contains(PosixFilePermission.OWNER_READ); + hasWritePermission = permissions.contains(PosixFilePermission.OWNER_WRITE); + } else { + // Get the group of the file + // Get POSIX file attributes and then group + PosixFileAttributes attrs = Files.readAttributes(path, PosixFileAttributes.class); + GroupPrincipal group = attrs.group(); + + // Check if besu user belongs to this group + boolean isMember = isGroupMember(besuUserName, group); + + if (isMember) { + // Group's permissions + hasReadPermission = permissions.contains(PosixFilePermission.GROUP_READ); + hasWritePermission = permissions.contains(PosixFilePermission.GROUP_WRITE); + } else { + // Others' permissions + hasReadPermission = permissions.contains(PosixFilePermission.OTHERS_READ); + hasWritePermission = permissions.contains(PosixFilePermission.OTHERS_WRITE); + } + } + + if (!hasReadPermission || (!readOnly && !hasWritePermission)) { + String accessType = readOnly ? "READ" : "READ_WRITE"; + commandLine.getOut().println("PERMISSION_CHECK_PATH:" + path + ":" + accessType); + } + } catch (Exception e) { + // Do nothing upon catching an error + commandLine + .getOut() + .println( + "Error: Failed to check permissions for path: '" + + path + + "'. Reason: " + + e.getMessage()); + } + } + + private static boolean isGroupMember(final String userName, final GroupPrincipal group) + throws IOException { + // Get the groups of the user by executing 'id -Gn username' + Process process = Runtime.getRuntime().exec(new String[] {"id", "-Gn", userName}); + BufferedReader reader = + new BufferedReader(new InputStreamReader(process.getInputStream(), UTF_8)); + + // Read the output of the command + String line = reader.readLine(); + boolean isMember = false; + if (line != null) { + // Split the groups + Iterable userGroups = Splitter.on(" ").split(line); + // Check if any of the user's groups match the file's group + + for (String grp : userGroups) { + if (grp.equals(group.getName())) { + isMember = true; + break; + } + } + } + return isMember; + } + @VisibleForTesting void setBesuConfiguration(final BesuConfigurationImpl pluginCommonConfiguration) { this.pluginCommonConfiguration = pluginCommonConfiguration; @@ -1390,8 +1530,8 @@ public class BesuCommand implements DefaultCommandValues, Runnable { } private void setReleaseMetrics() { - metricsSystem - .get() + besuComponent + .getMetricsSystem() .createLabelledGauge( StandardMetricCategory.PROCESS, "release", "Release information", "version") .labels(() -> 1, BesuInfo.version()); @@ -1855,7 +1995,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { .miningParameters(miningParametersSupplier.get()) .transactionPoolConfiguration(buildTransactionPoolConfiguration()) .nodeKey(new NodeKey(securityModule())) - .metricsSystem(metricsSystem.get()) + .metricsSystem((ObservableMetricsSystem) besuComponent.getMetricsSystem()) .messagePermissioningProviders(permissioningService.getMessagePermissioningProviders()) .privacyParameters(privacyParameters()) .clock(Clock.systemUTC()) @@ -1875,7 +2015,8 @@ public class BesuCommand implements DefaultCommandValues, Runnable { .randomPeerPriority(p2PDiscoveryOptionGroup.randomPeerPriority) .chainPruningConfiguration(unstableChainPruningOptions.toDomainObject()) .cacheLastBlocks(numberOfblocksToCache) - .genesisStateHashCacheEnabled(genesisStateHashCacheEnabled); + .genesisStateHashCacheEnabled(genesisStateHashCacheEnabled) + .besuComponent(besuComponent); } private JsonRpcConfiguration createEngineJsonRpcConfiguration( @@ -2277,7 +2418,6 @@ public class BesuCommand implements DefaultCommandValues, Runnable { p2pTLSConfiguration.ifPresent(runnerBuilder::p2pTLSConfiguration); - final ObservableMetricsSystem metricsSystem = this.metricsSystem.get(); final Runner runner = runnerBuilder .vertx(vertx) @@ -2304,7 +2444,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { .pidPath(pidPath) .dataDir(dataDir()) .bannedNodeIds(p2PDiscoveryOptionGroup.bannedNodeIds) - .metricsSystem(metricsSystem) + .metricsSystem((ObservableMetricsSystem) besuComponent.getMetricsSystem()) .permissioningService(permissioningService) .metricsConfiguration(metricsConfiguration) .staticNodes(staticNodes) @@ -2471,7 +2611,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { * @return Instance of MetricsSystem */ public MetricsSystem getMetricsSystem() { - return metricsSystem.get(); + return besuComponent.getMetricsSystem(); } private Set loadStaticNodes() throws IOException { @@ -2809,4 +2949,13 @@ public class BesuCommand implements DefaultCommandValues, Runnable { return builder.build(); } + + /** + * Returns the plugin context. + * + * @return the plugin context. + */ + public BesuPluginContextImpl getBesuPluginContext() { + return besuPluginContext; + } } diff --git a/besu/src/main/java/org/hyperledger/besu/components/BesuCommandModule.java b/besu/src/main/java/org/hyperledger/besu/components/BesuCommandModule.java index 86aa8c7594..59e2d60bf3 100644 --- a/besu/src/main/java/org/hyperledger/besu/components/BesuCommandModule.java +++ b/besu/src/main/java/org/hyperledger/besu/components/BesuCommandModule.java @@ -42,9 +42,7 @@ public class BesuCommandModule { @Provides @Singleton - BesuCommand provideBesuCommand( - final BesuPluginContextImpl pluginContext, - final @Named("besuCommandLogger") Logger commandLogger) { + BesuCommand provideBesuCommand(final @Named("besuCommandLogger") Logger commandLogger) { final BesuCommand besuCommand = new BesuCommand( RlpBlockImporter::new, @@ -52,7 +50,7 @@ public class BesuCommandModule { RlpBlockExporter::new, new RunnerBuilder(), new BesuController.Builder(), - pluginContext, + new BesuPluginContextImpl(), System.getenv(), commandLogger); besuCommand.toCommandLine(); @@ -71,4 +69,10 @@ public class BesuCommandModule { Logger provideBesuCommandLogger() { return Besu.getFirstLogger(); } + + @Provides + @Singleton + BesuPluginContextImpl provideBesuPluginContextImpl(final BesuCommand provideFrom) { + return provideFrom.getBesuPluginContext(); + } } diff --git a/besu/src/main/java/org/hyperledger/besu/components/BesuComponent.java b/besu/src/main/java/org/hyperledger/besu/components/BesuComponent.java index 9f810a6dc6..b0d5c3da0f 100644 --- a/besu/src/main/java/org/hyperledger/besu/components/BesuComponent.java +++ b/besu/src/main/java/org/hyperledger/besu/components/BesuComponent.java @@ -20,7 +20,7 @@ import org.hyperledger.besu.ethereum.eth.transactions.BlobCacheModule; import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.BonsaiCachedMerkleTrieLoader; import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.BonsaiCachedMerkleTrieLoaderModule; import org.hyperledger.besu.metrics.MetricsSystemModule; -import org.hyperledger.besu.metrics.ObservableMetricsSystem; +import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.services.BesuPluginContextImpl; import javax.inject.Named; @@ -60,7 +60,7 @@ public interface BesuComponent { * * @return ObservableMetricsSystem */ - ObservableMetricsSystem getObservableMetricsSystem(); + MetricsSystem getMetricsSystem(); /** * a Logger specifically configured to provide configuration feedback to users. diff --git a/besu/src/main/java/org/hyperledger/besu/components/BesuPluginContextModule.java b/besu/src/main/java/org/hyperledger/besu/components/BesuPluginContextModule.java index d62ab70224..702b63af19 100644 --- a/besu/src/main/java/org/hyperledger/besu/components/BesuPluginContextModule.java +++ b/besu/src/main/java/org/hyperledger/besu/components/BesuPluginContextModule.java @@ -14,9 +14,7 @@ */ package org.hyperledger.besu.components; -import org.hyperledger.besu.plugin.services.BesuConfiguration; import org.hyperledger.besu.services.BesuConfigurationImpl; -import org.hyperledger.besu.services.BesuPluginContextImpl; import javax.inject.Singleton; @@ -35,18 +33,4 @@ public class BesuPluginContextModule { BesuConfigurationImpl provideBesuPluginConfig() { return new BesuConfigurationImpl(); } - - /** - * Creates a BesuPluginContextImpl, used for plugin service discovery. - * - * @param pluginConfig the BesuConfigurationImpl - * @return the BesuPluginContext - */ - @Provides - @Singleton - public BesuPluginContextImpl provideBesuPluginContext(final BesuConfigurationImpl pluginConfig) { - BesuPluginContextImpl retval = new BesuPluginContextImpl(); - retval.addService(BesuConfiguration.class, pluginConfig); - return retval; - } } 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 bd10301a67..f6897fef35 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -555,7 +555,7 @@ public abstract class BesuControllerBuilder implements MiningParameterOverrides checkNotNull(evmConfiguration, "Missing evm config"); checkNotNull(networkingConfiguration, "Missing network configuration"); checkNotNull(dataStorageConfiguration, "Missing data storage configuration"); - + checkNotNull(besuComponent, "Must supply a BesuComponent"); prepForBuild(); final ProtocolSchedule protocolSchedule = createProtocolSchedule(); diff --git a/besu/src/main/scripts/besu-entry.sh b/besu/src/main/scripts/besu-entry.sh new file mode 100755 index 0000000000..ed3687b229 --- /dev/null +++ b/besu/src/main/scripts/besu-entry.sh @@ -0,0 +1,49 @@ +#!/bin/bash +## +## 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 +## + +# Run Besu first to get paths needing permission adjustment +output=$(/opt/besu/bin/besu --print-paths-and-exit $BESU_USER_NAME "$@") + +# Parse the output to find the paths and their required access types +echo "$output" | while IFS=: read -r prefix path accessType; do + if [[ "$prefix" == "PERMISSION_CHECK_PATH" ]]; then + # Change ownership to besu user and group + chown -R $BESU_USER_NAME:$BESU_USER_NAME $path + + # Ensure read/write permissions for besu user + + echo "Setting permissions for: $path with access: $accessType" + + if [[ "$accessType" == "READ" ]]; then + # Set read-only permissions for besu user + # Add execute for directories to allow access + find $path -type d -exec chmod u+rx {} \; + find $path -type f -exec chmod u+r {} \; + elif [[ "$accessType" == "READ_WRITE" ]]; then + # Set read/write permissions for besu user + # Add execute for directories to allow access + find $path -type d -exec chmod u+rwx {} \; + find $path -type f -exec chmod u+rw {} \; + fi + fi +done + +# Finally, run Besu with the actual arguments passed to the container +# Construct the command as a single string +COMMAND="/opt/besu/bin/besu $@" + +# Switch to the besu user and execute the command +exec su -s /bin/bash $BESU_USER_NAME -c "$COMMAND" 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 e6e0e85951..99b45ada1b 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java @@ -390,6 +390,7 @@ public class BesuCommandTest extends CommandTestAbstract { options.remove(spec.optionsMap().get("--config-file")); options.remove(spec.optionsMap().get("--help")); options.remove(spec.optionsMap().get("--version")); + options.remove(spec.optionsMap().get("--print-paths-and-exit")); for (final String tomlKey : tomlResult.keySet()) { final CommandLine.Model.OptionSpec optionSpec = spec.optionsMap().get("--" + tomlKey); diff --git a/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java b/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java index 3dd9641b48..5b1274389d 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java @@ -24,7 +24,6 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -71,6 +70,7 @@ import org.hyperledger.besu.ethereum.permissioning.PermissioningConfiguration; import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; import org.hyperledger.besu.plugin.services.PicoCLIOptions; import org.hyperledger.besu.plugin.services.StorageService; @@ -206,6 +206,9 @@ public abstract class CommandTestAbstract { @Mock(lenient = true) protected BesuController mockController; + @Mock(lenient = true) + protected BesuComponent mockBesuComponent; + @Mock protected RlpBlockExporter rlpBlockExporter; @Mock protected JsonBlockImporter jsonBlockImporter; @Mock protected RlpBlockImporter rlpBlockImporter; @@ -256,10 +259,8 @@ public abstract class CommandTestAbstract { @BeforeEach public void initMocks() throws Exception { - // doReturn used because of generic BesuController - doReturn(mockControllerBuilder) - .when(mockControllerBuilderFactory) - .fromEthNetworkConfig(any(), any()); + when(mockControllerBuilderFactory.fromEthNetworkConfig(any(), any())) + .thenReturn(mockControllerBuilder); when(mockControllerBuilder.synchronizerConfiguration(any())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.ethProtocolConfiguration(any())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.transactionPoolConfiguration(any())) @@ -288,14 +289,12 @@ public abstract class CommandTestAbstract { when(mockControllerBuilder.maxPeers(anyInt())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.maxRemotelyInitiatedPeers(anyInt())) .thenReturn(mockControllerBuilder); - when(mockControllerBuilder.besuComponent(any(BesuComponent.class))) - .thenReturn(mockControllerBuilder); + when(mockControllerBuilder.besuComponent(any())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.cacheLastBlocks(any())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.genesisStateHashCacheEnabled(any())) .thenReturn(mockControllerBuilder); - // doReturn used because of generic BesuController - doReturn(mockController).when(mockControllerBuilder).build(); + when(mockControllerBuilder.build()).thenReturn(mockController); lenient().when(mockController.getProtocolManager()).thenReturn(mockEthProtocolManager); lenient().when(mockController.getProtocolSchedule()).thenReturn(mockProtocolSchedule); lenient().when(mockController.getProtocolContext()).thenReturn(mockProtocolContext); @@ -350,6 +349,7 @@ public abstract class CommandTestAbstract { when(mockRunnerBuilder.allowedSubnets(any())).thenReturn(mockRunnerBuilder); when(mockRunnerBuilder.poaDiscoveryRetryBootnodes(anyBoolean())).thenReturn(mockRunnerBuilder); when(mockRunnerBuilder.build()).thenReturn(mockRunner); + when(mockBesuComponent.getMetricsSystem()).thenReturn(new NoOpMetricsSystem()); final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithmFactory.getInstance(); @@ -457,6 +457,7 @@ public abstract class CommandTestAbstract { besuCommand.parameterExceptionHandler(), besuCommand.executionExceptionHandler(), in, + mockBesuComponent, args); return besuCommand; } diff --git a/besu/src/test/java/org/hyperledger/besu/components/MockBesuCommandModule.java b/besu/src/test/java/org/hyperledger/besu/components/MockBesuCommandModule.java index 743b4ee8de..3695fe54f7 100644 --- a/besu/src/test/java/org/hyperledger/besu/components/MockBesuCommandModule.java +++ b/besu/src/test/java/org/hyperledger/besu/components/MockBesuCommandModule.java @@ -18,6 +18,9 @@ import static org.mockito.Mockito.mock; import org.hyperledger.besu.cli.BesuCommand; import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; +import org.hyperledger.besu.plugin.services.BesuConfiguration; +import org.hyperledger.besu.services.BesuConfigurationImpl; +import org.hyperledger.besu.services.BesuPluginContextImpl; import javax.inject.Named; import javax.inject.Singleton; @@ -47,4 +50,17 @@ public class MockBesuCommandModule { Logger provideBesuCommandLogger() { return LoggerFactory.getLogger(MockBesuCommandModule.class); } + + /** + * Creates a BesuPluginContextImpl, used for plugin service discovery. + * + * @return the BesuPluginContext + */ + @Provides + @Singleton + public BesuPluginContextImpl provideBesuPluginContext() { + BesuPluginContextImpl retval = new BesuPluginContextImpl(); + retval.addService(BesuConfiguration.class, new BesuConfigurationImpl()); + return retval; + } } diff --git a/build.gradle b/build.gradle index 792355417f..ee4392c9fc 100644 --- a/build.gradle +++ b/build.gradle @@ -1097,6 +1097,7 @@ distributions { from("build/reports/license/license-dependency.html") { into "." } from("./docs/GettingStartedBinaries.md") { into "." } from("./docs/DocsArchive0.8.0.html") { into "." } + from("./besu/src/main/scripts/besu-entry.sh") { into "./bin/" } from(autocomplete) { into "." } } } diff --git a/docker/Dockerfile b/docker/Dockerfile index c16345a82b..fe91c7026f 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -18,7 +18,8 @@ RUN apt-get update $NO_PROXY_CACHE && \ chown besu:besu /opt/besu && \ chmod 0755 /opt/besu -USER besu +ARG BESU_USER=besu +USER ${BESU_USER} WORKDIR /opt/besu COPY --chown=besu:besu besu /opt/besu/ @@ -43,7 +44,18 @@ ENV OTEL_RESOURCE_ATTRIBUTES="service.name=besu,service.version=$VERSION" ENV OLDPATH="${PATH}" ENV PATH="/opt/besu/bin:${OLDPATH}" -ENTRYPOINT ["besu"] + +# The entry script just sets permissions as needed based on besu config +# and is replaced by the besu process running as besu user. +# Suppressing this warning as there's no risk here because the root user +# only sets permissions and does not continue running the main process. +# hadolint ignore=DL3002 +USER root +RUN chmod +x /opt/besu/bin/besu-entry.sh + +ENV BESU_USER_NAME=${BESU_USER} + +ENTRYPOINT ["besu-entry.sh"] HEALTHCHECK --start-period=5s --interval=5s --timeout=1s --retries=10 CMD bash -c "[ -f /tmp/pid ]" # Build-time metadata as defined at http://label-schema.org diff --git a/docker/test.sh b/docker/test.sh index 6e08db13b5..e230f79ad1 100755 --- a/docker/test.sh +++ b/docker/test.sh @@ -41,4 +41,12 @@ bash $TEST_PATH/dgoss run --sysctl net.ipv6.conf.all.disable_ipv6=1 $DOCKER_IMAG --graphql-http-enabled \ > ./reports/01.xml || i=`expr $i + 1` +if [[ $i != 0 ]]; then exit $i; fi + +# Test for directory permissions +GOSS_FILES_PATH=$TEST_PATH/02 \ +bash $TEST_PATH/dgoss run --sysctl net.ipv6.conf.all.disable_ipv6=1 -v besu-data:/var/lib/besu $DOCKER_IMAGE --data-path=/var/lib/besu \ +--network=dev \ +> ./reports/02.xml || i=`expr $i + 1` + exit $i diff --git a/docker/tests/02/goss.yaml b/docker/tests/02/goss.yaml new file mode 100644 index 0000000000..d266cafa39 --- /dev/null +++ b/docker/tests/02/goss.yaml @@ -0,0 +1,10 @@ +--- +# runtime docker tests +file: + /var/lib/besu: + exists: true + owner: besu + mode: "0755" +process: + java: + running: true diff --git a/docker/tests/dgoss b/docker/tests/dgoss index 59bbc4683e..170270eff3 100755 --- a/docker/tests/dgoss +++ b/docker/tests/dgoss @@ -76,7 +76,7 @@ GOSS_PATH="${GOSS_PATH:-$(which goss 2> /dev/null || true)}" [[ $GOSS_PATH ]] || { error "Couldn't find goss installation, please set GOSS_PATH to it"; } [[ ${GOSS_OPTS+x} ]] || GOSS_OPTS="--color --format documentation" [[ ${GOSS_WAIT_OPTS+x} ]] || GOSS_WAIT_OPTS="-r 30s -s 1s > /dev/null" -GOSS_SLEEP=${GOSS_SLEEP:-0.2} +GOSS_SLEEP=${GOSS_SLEEP:-1.0} case "$1" in run) diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockTransactionSelector.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockTransactionSelector.java index 3d56c1fc07..e07b43b990 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockTransactionSelector.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockTransactionSelector.java @@ -401,7 +401,7 @@ public class BlockTransactionSelector { LOG.atTrace() .setMessage("Selected {} for block creation, evaluated in {}") .addArgument(transaction::toTraceLog) - .addArgument(evaluationContext.getPendingTransaction()) + .addArgument(evaluationContext.getEvaluationTimer()) .log(); return SELECTED; } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/cache/BonsaiCachedMerkleTrieLoaderModule.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/cache/BonsaiCachedMerkleTrieLoaderModule.java index 8ed7daa35f..b506d5a5ff 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/cache/BonsaiCachedMerkleTrieLoaderModule.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/cache/BonsaiCachedMerkleTrieLoaderModule.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache; import org.hyperledger.besu.metrics.ObservableMetricsSystem; +import org.hyperledger.besu.plugin.services.MetricsSystem; import dagger.Module; import dagger.Provides; @@ -24,7 +25,7 @@ public class BonsaiCachedMerkleTrieLoaderModule { @Provides BonsaiCachedMerkleTrieLoader provideCachedMerkleTrieLoaderModule( - final ObservableMetricsSystem metricsSystem) { - return new BonsaiCachedMerkleTrieLoader(metricsSystem); + final MetricsSystem metricsSystem) { + return new BonsaiCachedMerkleTrieLoader((ObservableMetricsSystem) metricsSystem); } } diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockchainSetupUtil.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockchainSetupUtil.java index 551f593e82..62d670c07a 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockchainSetupUtil.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockchainSetupUtil.java @@ -85,6 +85,13 @@ public class BlockchainSetupUtil { return blockchain; } + public Blockchain importAllBlocks( + final HeaderValidationMode headerValidationMode, + final HeaderValidationMode ommerValidationMode) { + importBlocks(blocks, headerValidationMode, ommerValidationMode); + return blockchain; + } + public void importFirstBlocks(final int count) { importBlocks(blocks.subList(0, count)); } @@ -126,6 +133,10 @@ public class BlockchainSetupUtil { return createForEthashChain(BlockTestUtil.getUpgradedForkResources(), DataStorageFormat.FOREST); } + public static BlockchainSetupUtil forSnapTesting(final DataStorageFormat storageFormat) { + return createForEthashChain(BlockTestUtil.getSnapTestChainResources(), storageFormat); + } + public static BlockchainSetupUtil createForEthashChain( final ChainResources chainResources, final DataStorageFormat storageFormat) { return create( @@ -241,6 +252,13 @@ public class BlockchainSetupUtil { } private void importBlocks(final List blocks) { + importBlocks(blocks, HeaderValidationMode.FULL, HeaderValidationMode.FULL); + } + + private void importBlocks( + final List blocks, + final HeaderValidationMode headerValidationMode, + final HeaderValidationMode ommerValidationMode) { for (final Block block : blocks) { if (block.getHeader().getNumber() == BlockHeader.GENESIS_BLOCK_NUMBER) { continue; @@ -248,7 +266,8 @@ public class BlockchainSetupUtil { final ProtocolSpec protocolSpec = protocolSchedule.getByBlockHeader(block.getHeader()); final BlockImporter blockImporter = protocolSpec.getBlockImporter(); final BlockImportResult result = - blockImporter.importBlock(protocolContext, block, HeaderValidationMode.FULL); + blockImporter.importBlock( + protocolContext, block, headerValidationMode, ommerValidationMode); if (!result.isImported()) { throw new IllegalStateException("Unable to import block " + block.getHeader().getNumber()); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServer.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServer.java index c1b855f2d6..7de933e137 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServer.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServer.java @@ -31,6 +31,7 @@ import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncConfiguration; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; import org.hyperledger.besu.ethereum.proof.WorldStateProofProvider; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.ethereum.trie.CompactEncoding; import org.hyperledger.besu.ethereum.trie.MerkleTrie; import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.BonsaiWorldStateProvider; @@ -240,12 +241,9 @@ class SnapServer implements BesuEvents.InitialSyncCompletionListener { stopWatch, maxResponseBytes, (pair) -> { - var rlpOutput = new BytesValueRLPOutput(); - rlpOutput.startList(); - rlpOutput.writeBytes(pair.getFirst()); - rlpOutput.writeRLPBytes(pair.getSecond()); - rlpOutput.endList(); - return rlpOutput.encodedSize(); + Bytes bytes = + AccountRangeMessage.toSlimAccount(RLP.input(pair.getSecond())); + return Hash.SIZE + bytes.size(); }); final Bytes32 endKeyBytes = range.endKeyHash(); @@ -257,11 +255,15 @@ class SnapServer implements BesuEvents.InitialSyncCompletionListener { storage.streamFlatAccounts(range.startKeyHash(), shouldContinuePredicate); if (accounts.isEmpty() && shouldContinuePredicate.shouldContinue.get()) { + var fromNextHash = + range.endKeyHash().compareTo(range.startKeyHash()) >= 0 + ? range.endKeyHash() + : range.startKeyHash(); // fetch next account after range, if it exists LOGGER.debug( "found no accounts in range, taking first value starting from {}", - asLogHash(range.endKeyHash())); - accounts = storage.streamFlatAccounts(range.endKeyHash(), UInt256.MAX_VALUE, 1L); + asLogHash(fromNextHash)); + accounts = storage.streamFlatAccounts(fromNextHash, UInt256.MAX_VALUE, 1L); } final var worldStateProof = diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/AccountRangeMessage.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/AccountRangeMessage.java index 8e1b1a31a5..71d0429592 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/AccountRangeMessage.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/AccountRangeMessage.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.ethereum.eth.messages.snap; +import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.AbstractSnapMessageData; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; @@ -28,6 +29,7 @@ import java.util.NavigableMap; import java.util.Optional; import java.util.TreeMap; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Maps; import kotlin.collections.ArrayDeque; import org.apache.tuweni.bytes.Bytes; @@ -117,7 +119,8 @@ public final class AccountRangeMessage extends AbstractSnapMessageData { return ImmutableAccountRangeData.builder().accounts(accounts).proofs(proofs).build(); } - private Bytes toFullAccount(final RLPInput rlpInput) { + @VisibleForTesting + public static Bytes toFullAccount(final RLPInput rlpInput) { final StateTrieAccountValue accountValue = StateTrieAccountValue.readFrom(rlpInput); final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput(); @@ -131,6 +134,26 @@ public final class AccountRangeMessage extends AbstractSnapMessageData { return rlpOutput.encoded(); } + public static Bytes toSlimAccount(final RLPInput rlpInput) { + StateTrieAccountValue accountValue = StateTrieAccountValue.readFrom(rlpInput); + var rlpOutput = new BytesValueRLPOutput(); + rlpOutput.startList(); + rlpOutput.writeLongScalar(accountValue.getNonce()); + rlpOutput.writeUInt256Scalar(accountValue.getBalance()); + if (accountValue.getStorageRoot().equals(Hash.EMPTY_TRIE_HASH)) { + rlpOutput.writeNull(); + } else { + rlpOutput.writeBytes(accountValue.getStorageRoot()); + } + if (accountValue.getCodeHash().equals(Hash.EMPTY)) { + rlpOutput.writeNull(); + } else { + rlpOutput.writeBytes(accountValue.getCodeHash()); + } + rlpOutput.endList(); + return rlpOutput.encoded(); + } + @Value.Immutable public interface AccountRangeData { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetAccountRangeMessage.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetAccountRangeMessage.java index 8f5fcaf9a7..6d8c8c128c 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetAccountRangeMessage.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetAccountRangeMessage.java @@ -24,6 +24,7 @@ import org.hyperledger.besu.ethereum.rlp.RLPInput; import java.math.BigInteger; import java.util.Optional; +import com.google.common.annotations.VisibleForTesting; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.immutables.value.Value; @@ -48,12 +49,21 @@ public final class GetAccountRangeMessage extends AbstractSnapMessageData { public static GetAccountRangeMessage create( final Hash worldStateRootHash, final Bytes32 startKeyHash, final Bytes32 endKeyHash) { + return create(worldStateRootHash, startKeyHash, endKeyHash, SIZE_REQUEST); + } + + @VisibleForTesting + public static GetAccountRangeMessage create( + final Hash worldStateRootHash, + final Bytes32 startKeyHash, + final Bytes32 endKeyHash, + final BigInteger sizeRequest) { final BytesValueRLPOutput tmp = new BytesValueRLPOutput(); tmp.startList(); tmp.writeBytes(worldStateRootHash); tmp.writeBytes(startKeyHash); tmp.writeBytes(endKeyHash); - tmp.writeBigIntegerScalar(SIZE_REQUEST); + tmp.writeBigIntegerScalar(sizeRequest); tmp.endList(); return new GetAccountRangeMessage(tmp.encoded()); } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServerGetAccountRangeTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServerGetAccountRangeTest.java new file mode 100644 index 0000000000..6d8180c8c0 --- /dev/null +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServerGetAccountRangeTest.java @@ -0,0 +1,495 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.manager.snap; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; +import org.hyperledger.besu.ethereum.eth.manager.EthMessages; +import org.hyperledger.besu.ethereum.eth.messages.snap.AccountRangeMessage; +import org.hyperledger.besu.ethereum.eth.messages.snap.GetAccountRangeMessage; +import org.hyperledger.besu.ethereum.eth.sync.snapsync.ImmutableSnapSyncConfiguration; +import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncConfiguration; +import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; +import org.hyperledger.besu.ethereum.trie.diffbased.common.DiffBasedWorldStateProvider; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; + +import java.math.BigInteger; +import java.util.NavigableMap; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class SnapServerGetAccountRangeTest { + private Hash rootHash; + public Bytes32 firstAccount; + public Bytes32 secondAccount; + private SnapServer snapServer; + private static ProtocolContext protocolContext; + + @BeforeAll + public static void setup() { + // Setup local blockchain for testing + BlockchainSetupUtil localBlockchainSetup = + BlockchainSetupUtil.forSnapTesting(DataStorageFormat.BONSAI); + localBlockchainSetup.importAllBlocks( + HeaderValidationMode.LIGHT_DETACHED_ONLY, HeaderValidationMode.LIGHT); + + protocolContext = localBlockchainSetup.getProtocolContext(); + } + + @BeforeEach + public void setupTest() { + WorldStateStorageCoordinator worldStateStorageCoordinator = + new WorldStateStorageCoordinator( + ((DiffBasedWorldStateProvider) protocolContext.getWorldStateArchive()) + .getWorldStateKeyValueStorage()); + + SnapSyncConfiguration snapSyncConfiguration = + ImmutableSnapSyncConfiguration.builder().isSnapServerEnabled(true).build(); + snapServer = + new SnapServer( + snapSyncConfiguration, + new EthMessages(), + worldStateStorageCoordinator, + protocolContext) + .start(); + initAccounts(); + } + + /** + * In this test, we request the entire state range, but limit the response to 4000 bytes. + * Expected: 86 accounts. + */ + @Test + public void test0_RequestEntireStateRangeWith4000BytesLimit() { + testAccountRangeRequest( + new AccountRangeRequestParams.Builder() + .rootHash(rootHash) + .responseBytes(4000) + .expectedAccounts(86) + .expectedFirstAccount(firstAccount) + .expectedLastAccount( + Bytes32.fromHexString( + "0x445cb5c1278fdce2f9cbdb681bdd76c52f8e50e41dbd9e220242a69ba99ac099")) + .build()); + } + + /** + * In this test, we request the entire state range, but limit the response to 3000 bytes. + * Expected: 65 accounts. + */ + @Test + public void test1_RequestEntireStateRangeWith3000BytesLimit() { + testAccountRangeRequest( + new AccountRangeRequestParams.Builder() + .rootHash(rootHash) + .responseBytes(3000) + .expectedAccounts(65) + .expectedFirstAccount(firstAccount) + .expectedLastAccount( + Bytes32.fromHexString( + "0x2e6fe1362b3e388184fd7bf08e99e74170b26361624ffd1c5f646da7067b58b6")) + .build()); + } + + /** + * In this test, we request the entire state range, but limit the response to 2000 bytes. + * Expected: 44 accounts. + */ + @Test + public void test2_RequestEntireStateRangeWith2000BytesLimit() { + testAccountRangeRequest( + new AccountRangeRequestParams.Builder() + .rootHash(rootHash) + .responseBytes(2000) + .expectedAccounts(44) + .expectedFirstAccount(firstAccount) + .expectedLastAccount( + Bytes32.fromHexString( + "0x1c3f74249a4892081ba0634a819aec9ed25f34c7653f5719b9098487e65ab595")) + .build()); + } + + /** + * In this test, we request the entire state range, but limit the response to 1 byte. The server + * should return the first account of the state. Expected: 1 account. + */ + @Test + public void test3_RequestEntireStateRangeWith1ByteLimit() { + testAccountRangeRequest( + new AccountRangeRequestParams.Builder() + .rootHash(rootHash) + .responseBytes(1) + .expectedAccounts(1) + .expectedFirstAccount(firstAccount) + .expectedLastAccount(firstAccount) + .build()); + } + + /** + * Here we request with a responseBytes limit of zero. The server should return one account. + * Expected: 1 account. + */ + @Test + public void test4_RequestEntireStateRangeWithZeroBytesLimit() { + testAccountRangeRequest( + new AccountRangeRequestParams.Builder() + .rootHash(rootHash) + .responseBytes(0) + .expectedAccounts(1) + .expectedFirstAccount(firstAccount) + .expectedLastAccount(firstAccount) + .build()); + } + + /** + * In this test, we request a range where startingHash is before the first available account key, + * and limitHash is after. The server should return the first and second account of the state + * (because the second account is the 'next available'). Expected: 2 accounts. + */ + @Test + public void test5_RequestRangeBeforeFirstAccountKey() { + testAccountRangeRequest( + new AccountRangeRequestParams.Builder() + .rootHash(rootHash) + .startHash(hashAdd(firstAccount, -500)) + .limitHash(hashAdd(firstAccount, 1)) + .expectedAccounts(2) + .expectedFirstAccount(firstAccount) + .expectedLastAccount(secondAccount) + .build()); + } + + /** + * Here we request range where both bounds are before the first available account key. This should + * return the first account (even though it's out of bounds). Expected: 1 account. + */ + @Test + public void test6_RequestRangeBothBoundsBeforeFirstAccountKey() { + testAccountRangeRequest( + new AccountRangeRequestParams.Builder() + .rootHash(rootHash) + .startHash(hashAdd(firstAccount, -500)) + .limitHash(hashAdd(firstAccount, -400)) + .expectedAccounts(1) + .expectedFirstAccount(firstAccount) + .expectedLastAccount(firstAccount) + .build()); + } + + /** + * In this test, both startingHash and limitHash are zero. The server should return the first + * available account. Expected: 1 account. + */ + @Test + public void test7_RequestBothBoundsZero() { + testAccountRangeRequest( + new AccountRangeRequestParams.Builder() + .rootHash(rootHash) + .startHash(Hash.ZERO) + .limitHash(Hash.ZERO) + .expectedAccounts(1) + .expectedFirstAccount(firstAccount) + .expectedLastAccount(firstAccount) + .build()); + } + + /** + * In this test, startingHash is exactly the first available account key. The server should return + * the first available account of the state as the first item. Expected: 86 accounts. + */ + @Test + public void test8_RequestStartingHashFirstAvailableAccountKey() { + testAccountRangeRequest( + new AccountRangeRequestParams.Builder() + .rootHash(rootHash) + .startHash(firstAccount) + .responseBytes(4000) + .expectedAccounts(86) + .expectedFirstAccount(firstAccount) + .expectedLastAccount( + Bytes32.fromHexString( + "0x445cb5c1278fdce2f9cbdb681bdd76c52f8e50e41dbd9e220242a69ba99ac099")) + .build()); + } + + /** + * In this test, startingHash is after the first available key. The server should return the + * second account of the state as the first item. Expected: 86 accounts. + */ + @Test + public void test9_RequestStartingHashAfterFirstAvailableKey() { + testAccountRangeRequest( + new AccountRangeRequestParams.Builder() + .rootHash(rootHash) + .startHash(secondAccount) + .responseBytes(4000) + .expectedAccounts(86) + .expectedFirstAccount(secondAccount) + .expectedLastAccount( + Bytes32.fromHexString( + "0x4615e5f5df5b25349a00ad313c6cd0436b6c08ee5826e33a018661997f85ebaa")) + .build()); + } + + /** This test requests a non-existent state root. Expected: 0 accounts. */ + @Test + public void test10_RequestNonExistentStateRoot() { + Hash rootHash = + Hash.fromHexString("1337000000000000000000000000000000000000000000000000000000000000"); + testAccountRangeRequest( + new AccountRangeRequestParams.Builder().rootHash(rootHash).expectedAccounts(0).build()); + } + + /** + * This test requests data at a state root that is 127 blocks old. We expect the server to have + * this state available. Expected: 84 accounts. + */ + @Test + public void test12_RequestStateRoot127BlocksOld() { + + Hash rootHash = + protocolContext + .getBlockchain() + .getBlockHeader((protocolContext.getBlockchain().getChainHeadBlockNumber() - 127)) + .orElseThrow() + .getStateRoot(); + testAccountRangeRequest( + new AccountRangeRequestParams.Builder() + .rootHash(rootHash) + .expectedAccounts(84) + .responseBytes(4000) + .expectedFirstAccount(firstAccount) + .expectedLastAccount( + Bytes32.fromHexString( + "0x580aa878e2f92d113a12c0a3ce3c21972b03dbe80786858d49a72097e2c491a3")) + .build()); + } + + /** + * This test requests data at a state root that is actually the storage root of an existing + * account. The server is supposed to ignore this request. Expected: 0 accounts. + */ + @Test + public void test13_RequestStateRootIsStorageRoot() { + Hash rootHash = + Hash.fromHexString("df97f94bc47471870606f626fb7a0b42eed2d45fcc84dc1200ce62f7831da990"); + testAccountRangeRequest( + new AccountRangeRequestParams.Builder().rootHash(rootHash).expectedAccounts(0).build()); + } + + /** + * In this test, the startingHash is after limitHash (wrong order). The server should ignore this + * invalid request. Expected: 0 accounts. + */ + @Test + public void test14_RequestStartingHashAfterLimitHash() { + testAccountRangeRequest( + new AccountRangeRequestParams.Builder() + .rootHash(rootHash) + .startHash(Hash.LAST) + .limitHash(Hash.ZERO) + .expectedAccounts(0) + .build()); + } + + /** + * In this test, the startingHash is the first available key, and limitHash is a key before + * startingHash (wrong order). The server should return the first available key. Expected: 1 + * account. + */ + @Test + public void test15_RequestStartingHashFirstAvailableKeyAndLimitHashBefore() { + testAccountRangeRequest( + new AccountRangeRequestParams.Builder() + .rootHash(rootHash) + .startHash(firstAccount) + .limitHash(hashAdd(firstAccount, -1)) + .expectedAccounts(1) + .expectedFirstAccount(firstAccount) + .expectedLastAccount(firstAccount) + .build()); + } + + /** + * In this test, the startingHash is the first available key and limitHash is zero. (wrong order). + * The server should return the first available key. Expected: 1 account. + */ + @Test + public void test16_RequestStartingHashFirstAvailableKeyAndLimitHashZero() { + testAccountRangeRequest( + new AccountRangeRequestParams.Builder() + .rootHash(rootHash) + .startHash(firstAccount) + .limitHash(Hash.ZERO) + .expectedAccounts(1) + .expectedFirstAccount(firstAccount) + .expectedLastAccount(firstAccount) + .build()); + } + + private void testAccountRangeRequest(final AccountRangeRequestParams params) { + NavigableMap accounts = getAccountRange(params); + assertThat(accounts.size()).isEqualTo(params.getExpectedAccounts()); + + if (params.getExpectedAccounts() > 0) { + assertThat(accounts.firstKey()).isEqualTo(params.getExpectedFirstAccount()); + assertThat(accounts.lastKey()).isEqualTo(params.getExpectedLastAccount()); + } + } + + private NavigableMap getAccountRange(final AccountRangeRequestParams params) { + Hash rootHash = params.getRootHash(); + Bytes32 startHash = params.getStartHash(); + Bytes32 limitHash = params.getLimitHash(); + BigInteger sizeRequest = BigInteger.valueOf(params.getResponseBytes()); + + GetAccountRangeMessage requestMessage = + GetAccountRangeMessage.create(rootHash, startHash, limitHash, sizeRequest); + + AccountRangeMessage resultMessage = + AccountRangeMessage.readFrom( + snapServer.constructGetAccountRangeResponse( + requestMessage.wrapMessageData(BigInteger.ONE))); + NavigableMap accounts = resultMessage.accountData(false).accounts(); + return accounts; + } + + @SuppressWarnings("UnusedVariable") + private void initAccounts() { + rootHash = protocolContext.getWorldStateArchive().getMutable().rootHash(); + GetAccountRangeMessage requestMessage = + GetAccountRangeMessage.create(rootHash, Hash.ZERO, Hash.LAST, BigInteger.valueOf(4000)); + AccountRangeMessage resultMessage = + AccountRangeMessage.readFrom( + snapServer.constructGetAccountRangeResponse( + requestMessage.wrapMessageData(BigInteger.ONE))); + NavigableMap accounts = resultMessage.accountData(false).accounts(); + firstAccount = accounts.firstEntry().getKey(); + secondAccount = + accounts.entrySet().stream().skip(1).findFirst().orElse(accounts.firstEntry()).getKey(); + } + + private Bytes32 hashAdd(final Bytes32 hash, final int value) { + var result = Hash.wrap(hash).toBigInteger().add(BigInteger.valueOf(value)); + Bytes resultBytes = Bytes.wrap(result.toByteArray()); + return Bytes32.leftPad(resultBytes); + } + + public static class AccountRangeRequestParams { + private final Hash rootHash; + private final Bytes32 startHash; + private final Bytes32 limitHash; + private final int responseBytes; + private final int expectedAccounts; + private final Bytes32 expectedFirstAccount; + private final Bytes32 expectedLastAccount; + + private AccountRangeRequestParams(final Builder builder) { + this.rootHash = builder.rootHash; + this.startHash = builder.startHash; + this.limitHash = builder.limitHash; + this.responseBytes = builder.responseBytes; + this.expectedAccounts = builder.expectedAccounts; + this.expectedFirstAccount = builder.expectedFirstAccount; + this.expectedLastAccount = builder.expectedLastAccount; + } + + public static class Builder { + private Hash rootHash = null; + private Bytes32 startHash = Bytes32.ZERO; + private Bytes32 limitHash = Hash.LAST; + private int responseBytes = Integer.MAX_VALUE; + private int expectedAccounts = 0; + private Bytes32 expectedFirstAccount = null; + private Bytes32 expectedLastAccount = null; + + public Builder rootHash(final Hash rootHashHex) { + this.rootHash = rootHashHex; + return this; + } + + public Builder startHash(final Bytes32 startHashHex) { + this.startHash = startHashHex; + return this; + } + + public Builder limitHash(final Bytes32 limitHashHex) { + this.limitHash = limitHashHex; + return this; + } + + public Builder responseBytes(final int responseBytes) { + this.responseBytes = responseBytes; + return this; + } + + public Builder expectedAccounts(final int expectedAccounts) { + this.expectedAccounts = expectedAccounts; + return this; + } + + public Builder expectedFirstAccount(final Bytes32 expectedFirstAccount) { + this.expectedFirstAccount = expectedFirstAccount; + return this; + } + + public Builder expectedLastAccount(final Bytes32 expectedLastAccount) { + this.expectedLastAccount = expectedLastAccount; + return this; + } + + public AccountRangeRequestParams build() { + return new AccountRangeRequestParams(this); + } + } + + // Getters for each field + public Hash getRootHash() { + return rootHash; + } + + public Bytes32 getStartHash() { + return startHash; + } + + public Bytes32 getLimitHash() { + return limitHash; + } + + public int getResponseBytes() { + return responseBytes; + } + + public int getExpectedAccounts() { + return expectedAccounts; + } + + public Bytes32 getExpectedFirstAccount() { + return expectedFirstAccount; + } + + public Bytes32 getExpectedLastAccount() { + return expectedLastAccount; + } + } +} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServerTest.java index a41da1ef6b..e168b7e2fe 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServerTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServerTest.java @@ -188,7 +188,7 @@ public class SnapServerTest { public void assertAccountLimitRangeResponse() { // assert we limit the range response according to size final int acctCount = 2000; - final long acctRLPSize = 105; + final long acctRLPSize = 37; List randomLoad = IntStream.range(1, 4096).boxed().collect(Collectors.toList()); Collections.shuffle(randomLoad); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/AccountRangeMessageTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/AccountRangeMessageTest.java index bd715b6459..c7e5bf74c4 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/AccountRangeMessageTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/AccountRangeMessageTest.java @@ -14,11 +14,15 @@ */ package org.hyperledger.besu.ethereum.eth.messages.snap; +import static org.assertj.core.api.Assertions.assertThat; + import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.RawMessage; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.rlp.RLPInput; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; import java.util.ArrayList; @@ -28,7 +32,6 @@ import java.util.Map; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; public final class AccountRangeMessageTest { @@ -51,7 +54,81 @@ public final class AccountRangeMessageTest { // check match originals. final AccountRangeMessage.AccountRangeData range = message.accountData(false); - Assertions.assertThat(range.accounts()).isEqualTo(keys); - Assertions.assertThat(range.proofs()).isEqualTo(proofs); + assertThat(range.accounts()).isEqualTo(keys); + assertThat(range.proofs()).isEqualTo(proofs); + } + + @Test + public void toSlimAccountTest() { + // Initialize nonce and balance + long nonce = 1L; + Wei balance = Wei.of(2L); + + // Create a StateTrieAccountValue with the given nonce and balance + final StateTrieAccountValue accountValue = + new StateTrieAccountValue(nonce, balance, Hash.EMPTY_TRIE_HASH, Hash.EMPTY); + + // Encode the account value to RLP + final BytesValueRLPOutput rlpOut = new BytesValueRLPOutput(); + accountValue.writeTo(rlpOut); + + // Convert the encoded account value to a slim account representation + Bytes slimAccount = AccountRangeMessage.toSlimAccount(RLP.input(rlpOut.encoded())); + + // Read the slim account RLP input + RLPInput in = RLP.input(slimAccount); + in.enterList(); + + // Verify the nonce and balance + final long expectedNonce = in.readLongScalar(); + final Wei expectedWei = Wei.of(in.readUInt256Scalar()); + assertThat(expectedNonce).isEqualTo(nonce); + assertThat(expectedWei).isEqualTo(balance); + + // Check that the storageRoot is empty + assertThat(in.nextIsNull()).isTrue(); + in.skipNext(); + + // Check that the codeHash is empty + assertThat(in.nextIsNull()).isTrue(); + in.skipNext(); + + // Exit the list + in.leaveList(); + } + + @Test + public void toFullAccountTest() { + // Initialize nonce and balance + long nonce = 1L; + Wei balance = Wei.of(2L); + + // Create a StateTrieAccountValue with the given nonce and balance + final StateTrieAccountValue accountValue = + new StateTrieAccountValue(nonce, balance, Hash.EMPTY_TRIE_HASH, Hash.EMPTY); + + // Encode the account value to RLP + final BytesValueRLPOutput rlpOut = new BytesValueRLPOutput(); + accountValue.writeTo(rlpOut); + + // Convert the encoded account value to a full account representation + Bytes fullAccount = AccountRangeMessage.toFullAccount(RLP.input(rlpOut.encoded())); + + // Read the full account RLP input + RLPInput in = RLP.input(fullAccount); + in.enterList(); + + // Verify the nonce and balance + final long expectedNonce = in.readLongScalar(); + final Wei expectedWei = Wei.of(in.readUInt256Scalar()); + assertThat(expectedNonce).isEqualTo(nonce); + assertThat(expectedWei).isEqualTo(balance); + + // Verify the storageRoot and codeHash + assertThat(in.readBytes32()).isEqualTo(Hash.EMPTY_TRIE_HASH); + assertThat(in.readBytes32()).isEqualTo(Hash.EMPTY); + + // Exit the list + in.leaveList(); } } diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricsSystemModule.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricsSystemModule.java index 044085ef42..1347d6faaa 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricsSystemModule.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricsSystemModule.java @@ -36,10 +36,4 @@ public class MetricsSystemModule { MetricsSystem provideMetricsSystem(final MetricsConfiguration metricsConfig) { return MetricsSystemFactory.create(metricsConfig); } - - @Provides - @Singleton - ObservableMetricsSystem provideObservableMetricsSystem(final MetricsConfiguration metricsConfig) { - return MetricsSystemFactory.create(metricsConfig); - } } diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetrySystem.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetrySystem.java index ca1dc5dd3a..a399b28373 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetrySystem.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetrySystem.java @@ -41,6 +41,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.function.DoubleSupplier; import java.util.stream.Stream; +import javax.inject.Singleton; import com.google.common.collect.ImmutableSet; import io.opentelemetry.api.common.AttributeKey; @@ -67,6 +68,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Metrics system relying on the native OpenTelemetry format. */ +@Singleton public class OpenTelemetrySystem implements ObservableMetricsSystem { private static final Logger LOG = LoggerFactory.getLogger(OpenTelemetrySystem.class); diff --git a/testutil/src/main/java/org/hyperledger/besu/testutil/BlockTestUtil.java b/testutil/src/main/java/org/hyperledger/besu/testutil/BlockTestUtil.java index 9f31283bfc..4c45d3cf74 100644 --- a/testutil/src/main/java/org/hyperledger/besu/testutil/BlockTestUtil.java +++ b/testutil/src/main/java/org/hyperledger/besu/testutil/BlockTestUtil.java @@ -52,6 +52,8 @@ public final class BlockTestUtil { Suppliers.memoize(BlockTestUtil::supplyUpgradedForkResources); private static final Supplier testRpcCompactChainSupplier = Suppliers.memoize(BlockTestUtil::supplyTestRpcCompactResources); + private static final Supplier snapTestChainSupplier = + Suppliers.memoize(BlockTestUtil::supplySnapTestChainResources); /** * Gets test blockchain url. @@ -156,6 +158,15 @@ public final class BlockTestUtil { return testRpcCompactChainSupplier.get(); } + /** + * Gets test chain resources for Snap tests. + * + * @return the test chain resources + */ + public static ChainResources getSnapTestChainResources() { + return snapTestChainSupplier.get(); + } + private static ChainResources supplyTestChainResources() { final URL genesisURL = ensureFileUrl(BlockTestUtil.class.getClassLoader().getResource("testGenesis.json")); @@ -164,6 +175,15 @@ public final class BlockTestUtil { return new ChainResources(genesisURL, blocksURL); } + private static ChainResources supplySnapTestChainResources() { + final URL genesisURL = + ensureFileUrl(BlockTestUtil.class.getClassLoader().getResource("snap/snapGenesis.json")); + final URL blocksURL = + ensureFileUrl( + BlockTestUtil.class.getClassLoader().getResource("snap/testBlockchain.blocks")); + return new ChainResources(genesisURL, blocksURL); + } + private static ChainResources supplyHiveTestChainResources() { final URL genesisURL = ensureFileUrl(BlockTestUtil.class.getClassLoader().getResource("hive/testGenesis.json")); diff --git a/testutil/src/main/resources/snap/snapGenesis.json b/testutil/src/main/resources/snap/snapGenesis.json new file mode 100644 index 0000000000..20979e7f74 --- /dev/null +++ b/testutil/src/main/resources/snap/snapGenesis.json @@ -0,0 +1,111 @@ +{ + "config": { + "ethash": {}, + "chainID": 3503995874084926, + "homesteadBlock": 0, + "eip150Block": 6, + "eip155Block": 12, + "eip158Block": 12, + "byzantiumBlock": 18, + "constantinopleBlock": 24, + "constantinopleFixBlock": 30, + "istanbulBlock": 36, + "muirGlacierBlock": 42, + "berlinBlock": 48, + "londonBlock": 54, + "arrowGlacierBlock": 60, + "grayGlacierBlock": 66, + "mergeNetsplitBlock": 72, + "terminalTotalDifficulty": 9454784, + "shanghaiTime": 780, + "cancunTime": 840 + }, + "nonce": "0x0", + "timestamp": "0x0", + "extraData": "0x68697665636861696e", + "gasLimit": "0x23f3e20", + "difficulty": "0x20000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "alloc": { + "000f3df6d732807ef1319fb7b8bb8522d0beac02": { + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500", + "balance": "0x2a" + }, + "0c2c51a0990aee1d73c1228de158688341557508": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "14e46043e63d0e3cdcf2530519f4cfaf35058cb2": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "16c57edf7fa9d9525378b0b81bf8a3ced0620c1c": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "1f4924b14f34e24159387c0a4cdbaa32f3ddb0cf": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "1f5bde34b4afc686f136c7a3cb6ec376f7357759": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "2d389075be5be9f2246ad654ce152cf05990b209": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "3ae75c08b4c907eb63a8960c45b86e1e9ab6123c": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "4340ee1b812acb40a1eb561c019c327b243b92df": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "4a0f1452281bcec5bd90c3dce6162a5995bfe9df": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "4dde844b71bcdf95512fb4dc94e84fb67b512ed8": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "5f552da00dfb4d3749d9e62dcee3c918855a86a0": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "654aa64f5fbefb84c270ec74211b81ca8c44a72e": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "717f8aa2b982bee0e29f573d31df288663e1ce16": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "7435ed30a8b4aeb0877cef0c6e8cffe834eb865f": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "83c7e323d189f18725ac510004fdc2941f8c4a78": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "84e75c28348fb86acea1a93a39426d7d60f4cc46": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "8bebc8ba651aee624937e7d897853ac30c95a067": { + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x0000000000000000000000000000000000000000000000000000000000000003" + }, + "balance": "0x1", + "nonce": "0x1" + }, + "c7b99a164efd027a93f147376cc7da7c67c6bbe0": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "d803681e487e6ac18053afc5a6cd813c86ec3e4d": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "e7d13f7aa2a838d24c59b40186a0aca1e21cffcc": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "eda8645ba6948855e3b3cd596bbb07596d59c603": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + } + }, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "baseFeePerGas": null, + "excessBlobGas": null, + "blobGasUsed": null +} \ No newline at end of file diff --git a/testutil/src/main/resources/snap/testBlockchain.blocks b/testutil/src/main/resources/snap/testBlockchain.blocks new file mode 100644 index 0000000000..0df8d2f63a Binary files /dev/null and b/testutil/src/main/resources/snap/testBlockchain.blocks differ