Merge branch 'main' into 7311-add-cli-feature-toggle-for-peertask-system

pull/7633/head
Matilda-Clerke 2 months ago committed by GitHub
commit bc11e0c8bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      CHANGELOG.md
  2. 125
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java
  3. 23
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java
  4. 5
      besu/src/main/java/org/hyperledger/besu/Besu.java
  5. 175
      besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
  6. 12
      besu/src/main/java/org/hyperledger/besu/components/BesuCommandModule.java
  7. 4
      besu/src/main/java/org/hyperledger/besu/components/BesuComponent.java
  8. 16
      besu/src/main/java/org/hyperledger/besu/components/BesuPluginContextModule.java
  9. 2
      besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java
  10. 49
      besu/src/main/scripts/besu-entry.sh
  11. 1
      besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java
  12. 7
      besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java
  13. 16
      besu/src/test/java/org/hyperledger/besu/components/MockBesuCommandModule.java
  14. 1
      build.gradle
  15. 16
      docker/Dockerfile
  16. 8
      docker/test.sh
  17. 10
      docker/tests/02/goss.yaml
  18. 2
      docker/tests/dgoss
  19. 2
      ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockTransactionSelector.java
  20. 5
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/cache/BonsaiCachedMerkleTrieLoaderModule.java
  21. 21
      ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockchainSetupUtil.java
  22. 18
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServer.java
  23. 25
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/AccountRangeMessage.java
  24. 12
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetAccountRangeMessage.java
  25. 495
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServerGetAccountRangeTest.java
  26. 2
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServerTest.java
  27. 83
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/AccountRangeMessageTest.java
  28. 6
      metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricsSystemModule.java
  29. 2
      metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetrySystem.java
  30. 20
      testutil/src/main/java/org/hyperledger/besu/testutil/BlockTestUtil.java
  31. 111
      testutil/src/main/resources/snap/snapGenesis.json
  32. BIN
      testutil/src/main/resources/snap/testBlockchain.blocks

@ -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

@ -75,6 +75,70 @@ public class ProcessBesuNodeRunner implements BesuNodeRunner {
final Path dataDir = node.homeDirectory();
final List<String> 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=<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<String> commandlineArgs(final BesuNode node, final Path dataDir) {
final List<String> 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=<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) {

@ -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<EnodeURL> 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

@ -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);

@ -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<String> identityString = Optional.empty();
private Boolean printPathsAndExit = Boolean.FALSE;
private String besuUserName = "besu";
@Option(
names = "--print-paths-and-exit",
paramLabel = "<username>",
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<ObservableMetricsSystem> 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<PosixFilePermission> 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<String> 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<EnodeURL> 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;
}
}

@ -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();
}
}

@ -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.

@ -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;
}
}

@ -552,7 +552,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();

@ -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"

@ -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);

@ -42,6 +42,7 @@ import org.hyperledger.besu.cli.options.unstable.EthProtocolOptions;
import org.hyperledger.besu.cli.options.unstable.MetricsCLIOptions;
import org.hyperledger.besu.cli.options.unstable.NetworkingOptions;
import org.hyperledger.besu.cli.options.unstable.SynchronizerOptions;
import org.hyperledger.besu.components.BesuComponent;
import org.hyperledger.besu.config.GenesisConfigOptions;
import org.hyperledger.besu.controller.BesuController;
import org.hyperledger.besu.controller.BesuControllerBuilder;
@ -69,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;
@ -204,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;
@ -344,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();
@ -451,6 +457,7 @@ public abstract class CommandTestAbstract {
besuCommand.parameterExceptionHandler(),
besuCommand.executionExceptionHandler(),
in,
mockBesuComponent,
args);
return besuCommand;
}

@ -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;
}
}

@ -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 "." }
}
}

@ -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

@ -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

@ -0,0 +1,10 @@
---
# runtime docker tests
file:
/var/lib/besu:
exists: true
owner: besu
mode: "0755"
process:
java:
running: true

@ -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)

@ -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;
}

@ -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);
}
}

@ -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<Block> blocks) {
importBlocks(blocks, HeaderValidationMode.FULL, HeaderValidationMode.FULL);
}
private void importBlocks(
final List<Block> 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());
}

@ -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 =

@ -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 {

@ -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());
}

@ -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<Bytes32, Bytes> 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<Bytes32, Bytes> 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<Bytes32, Bytes> 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<Bytes32, Bytes> 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;
}
}
}

@ -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<Integer> randomLoad = IntStream.range(1, 4096).boxed().collect(Collectors.toList());
Collections.shuffle(randomLoad);

@ -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();
}
}

@ -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);
}
}

@ -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);

@ -52,6 +52,8 @@ public final class BlockTestUtil {
Suppliers.memoize(BlockTestUtil::supplyUpgradedForkResources);
private static final Supplier<ChainResources> testRpcCompactChainSupplier =
Suppliers.memoize(BlockTestUtil::supplyTestRpcCompactResources);
private static final Supplier<ChainResources> 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"));

@ -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
}
Loading…
Cancel
Save