PAN-2765 Implement dump command to dump a specific block from storage (#1641)

* add dump sub command

* add getBlockHash method for the dump sub command

* Resolve review issues :  Add the ability to export multiple blocks, Rename dump command to export command, Improve tests for the export command

Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>
pull/2/head
Karim T 5 years ago committed by Abdelhamid Bakhta
parent b4ee866cf6
commit 6412c4e028
  1. 10
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/chain/Blockchain.java
  2. 2
      pantheon/src/main/java/tech/pegasys/pantheon/Pantheon.java
  3. 1
      pantheon/src/main/java/tech/pegasys/pantheon/cli/DefaultCommandValues.java
  4. 7
      pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java
  5. 155
      pantheon/src/main/java/tech/pegasys/pantheon/cli/subcommands/blocks/BlocksSubCommand.java
  6. 88
      pantheon/src/main/java/tech/pegasys/pantheon/util/BlockExporter.java
  7. 153
      pantheon/src/test/java/tech/pegasys/pantheon/cli/BlockSubCommandTest.java
  8. 6
      pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java
  9. 111
      pantheon/src/test/java/tech/pegasys/pantheon/util/BlockExporterTest.java

@ -67,12 +67,14 @@ public interface Blockchain {
final Hash genesisHash =
getBlockHashByNumber(BlockHeader.GENESIS_BLOCK_NUMBER)
.orElseThrow(() -> new IllegalStateException("Missing genesis block."));
return getBlockByHash(genesisHash);
}
default Block getBlockByHash(final Hash blockHash) {
final BlockHeader header =
getBlockHeader(genesisHash)
.orElseThrow(() -> new IllegalStateException("Missing genesis block."));
getBlockHeader(blockHash).orElseThrow(() -> new IllegalStateException("Missing block."));
final BlockBody body =
getBlockBody(genesisHash)
.orElseThrow(() -> new IllegalStateException("Missing genesis block."));
getBlockBody(blockHash).orElseThrow(() -> new IllegalStateException("Missing block."));
return new Block(header, body);
}

@ -17,6 +17,7 @@ import static org.apache.logging.log4j.LogManager.getLogger;
import tech.pegasys.pantheon.cli.PantheonCommand;
import tech.pegasys.pantheon.controller.PantheonController;
import tech.pegasys.pantheon.services.PantheonPluginContextImpl;
import tech.pegasys.pantheon.util.BlockExporter;
import tech.pegasys.pantheon.util.BlockImporter;
import org.apache.logging.log4j.Logger;
@ -35,6 +36,7 @@ public final class Pantheon {
new PantheonCommand(
logger,
new BlockImporter(),
new BlockExporter(),
new RunnerBuilder(),
new PantheonController.Builder(),
new PantheonPluginContextImpl(),

@ -35,6 +35,7 @@ public interface DefaultCommandValues {
String PANTHEON_HOME_PROPERTY_NAME = "pantheon.home";
String DEFAULT_DATA_DIR_PATH = "./build/data";
String MANDATORY_INTEGER_FORMAT_HELP = "<INTEGER>";
String MANDATORY_LONG_FORMAT_HELP = "<LONG>";
String MANDATORY_MODE_FORMAT_HELP = "<MODE>";
String MANDATORY_NETWORK_FORMAT_HELP = "<NETWORK>";
String MANDATORY_NODE_ID_FORMAT_HELP = "<NODEID>";

@ -87,6 +87,7 @@ import tech.pegasys.pantheon.services.PantheonEventsImpl;
import tech.pegasys.pantheon.services.PantheonPluginContextImpl;
import tech.pegasys.pantheon.services.PicoCLIOptionsImpl;
import tech.pegasys.pantheon.services.kvstore.RocksDbConfiguration;
import tech.pegasys.pantheon.util.BlockExporter;
import tech.pegasys.pantheon.util.BlockImporter;
import tech.pegasys.pantheon.util.InvalidConfigurationException;
import tech.pegasys.pantheon.util.PermissioningConfigurationValidator;
@ -150,6 +151,7 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
private CommandLine commandLine;
private final BlockImporter blockImporter;
private final BlockExporter blockExporter;
final NetworkingOptions networkingOptions = NetworkingOptions.create();
final SynchronizerOptions synchronizerOptions = SynchronizerOptions.create();
@ -601,12 +603,14 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
public PantheonCommand(
final Logger logger,
final BlockImporter blockImporter,
final BlockExporter blockExporter,
final RunnerBuilder runnerBuilder,
final PantheonController.Builder controllerBuilderFactory,
final PantheonPluginContextImpl pantheonPluginContext,
final Map<String, String> environment) {
this.logger = logger;
this.blockImporter = blockImporter;
this.blockExporter = blockExporter;
this.runnerBuilder = runnerBuilder;
this.controllerBuilderFactory = controllerBuilderFactory;
this.pantheonPluginContext = pantheonPluginContext;
@ -651,7 +655,8 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
private PantheonCommand addSubCommands(
final AbstractParseResultHandler<List<Object>> resultHandler, final InputStream in) {
commandLine.addSubcommand(
BlocksSubCommand.COMMAND_NAME, new BlocksSubCommand(blockImporter, resultHandler.out()));
BlocksSubCommand.COMMAND_NAME,
new BlocksSubCommand(blockImporter, blockExporter, resultHandler.out()));
commandLine.addSubcommand(
PublicKeySubCommand.COMMAND_NAME,
new PublicKeySubCommand(resultHandler.out(), getKeyLoader()));

@ -13,20 +13,28 @@
package tech.pegasys.pantheon.cli.subcommands.blocks;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.nio.charset.StandardCharsets.UTF_8;
import static tech.pegasys.pantheon.cli.DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP;
import static tech.pegasys.pantheon.cli.DefaultCommandValues.MANDATORY_LONG_FORMAT_HELP;
import static tech.pegasys.pantheon.cli.subcommands.blocks.BlocksSubCommand.COMMAND_NAME;
import tech.pegasys.pantheon.cli.PantheonCommand;
import tech.pegasys.pantheon.cli.subcommands.blocks.BlocksSubCommand.ExportSubCommand;
import tech.pegasys.pantheon.cli.subcommands.blocks.BlocksSubCommand.ImportSubCommand;
import tech.pegasys.pantheon.ethereum.core.Block;
import tech.pegasys.pantheon.metrics.prometheus.MetricsConfiguration;
import tech.pegasys.pantheon.metrics.prometheus.MetricsService;
import tech.pegasys.pantheon.util.BlockExporter;
import tech.pegasys.pantheon.util.BlockImporter;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import io.vertx.core.Vertx;
@ -45,8 +53,9 @@ import picocli.CommandLine.Spec;
name = COMMAND_NAME,
description = "This command provides blocks related actions.",
mixinStandardHelpOptions = true,
subcommands = {ImportSubCommand.class})
subcommands = {ImportSubCommand.class, ExportSubCommand.class})
public class BlocksSubCommand implements Runnable {
private static final Logger LOG = LogManager.getLogger();
public static final String COMMAND_NAME = "blocks";
@ -60,10 +69,14 @@ public class BlocksSubCommand implements Runnable {
private CommandSpec spec; // Picocli injects reference to command spec
private final BlockImporter blockImporter;
private final BlockExporter blockExporter;
private final PrintStream out;
public BlocksSubCommand(final BlockImporter blockImporter, final PrintStream out) {
public BlocksSubCommand(
final BlockImporter blockImporter, final BlockExporter blockExporter, final PrintStream out) {
this.blockImporter = blockImporter;
this.blockExporter = blockExporter;
this.out = out;
}
@ -98,24 +111,12 @@ public class BlocksSubCommand implements Runnable {
public void run() {
LOG.info("Runs import sub command with blocksImportFile : {}", blocksImportFile);
checkNotNull(parentCommand);
checkNotNull(parentCommand.parentCommand);
checkCommand(parentCommand);
checkNotNull(parentCommand.blockImporter);
Optional<MetricsService> metricsService = Optional.empty();
try {
final MetricsConfiguration metricsConfiguration =
parentCommand.parentCommand.metricsConfiguration();
if (metricsConfiguration.isEnabled() || metricsConfiguration.isPushEnabled()) {
metricsService =
Optional.of(
MetricsService.create(
Vertx.vertx(),
metricsConfiguration,
parentCommand.parentCommand.getMetricsSystem()));
metricsService.ifPresent(MetricsService::start);
}
Optional<MetricsService> metricsService = initMetrics(parentCommand);
try {
// As blocksImportFile even if initialized as null is injected by PicoCLI and param is
// mandatory
// So we are sure it's always not null, we can remove the warning
@ -134,5 +135,125 @@ public class BlocksSubCommand implements Runnable {
metricsService.ifPresent(MetricsService::stop);
}
}
private static void checkCommand(final BlocksSubCommand parentCommand) {
checkNotNull(parentCommand);
checkNotNull(parentCommand.parentCommand);
}
}
/**
* blocks export sub-command
*
* <p>Export a block list from storage
*/
@Command(
name = "export",
description = "This command export a specific block from storage",
mixinStandardHelpOptions = true)
static class ExportSubCommand implements Runnable {
@SuppressWarnings("unused")
@ParentCommand
private BlocksSubCommand parentCommand; // Picocli injects reference to parent command
@Option(
names = "--start-block",
required = true,
paramLabel = MANDATORY_LONG_FORMAT_HELP,
description = "the starting index of the block list to export (inclusive)",
arity = "1..1")
private final Long startBlock = null;
@Option(
names = "--end-block",
paramLabel = MANDATORY_LONG_FORMAT_HELP,
description =
"the ending index of the block list to export (exclusive), "
+ "if not specified a single block will be export",
arity = "1..1")
private final Long endBlock = null;
@Option(
names = "--to",
paramLabel = MANDATORY_FILE_FORMAT_HELP,
description = "File to write the block list instead of standard output",
arity = "1..1")
private File blocksExportFile = null;
@Override
public void run() {
LOG.info("Runs export sub command");
checkCommand(this, startBlock, endBlock);
Optional<MetricsService> metricsService = initMetrics(parentCommand);
try {
final BlockExporter.ExportResult exportResult =
parentCommand.blockExporter.exportBlockchain(
parentCommand.parentCommand.buildController(), startBlock, endBlock);
outputBlock(exportResult.blocks);
if (exportResult.blocks.isEmpty()) {
throw new ExecutionException(new CommandLine(this), "No block found at the given index");
} else if (!exportResult.allBlocksAreFound) {
throw new ExecutionException(
new CommandLine(this),
"Partial export due to inability to recover all requested blocks");
}
} finally {
metricsService.ifPresent(MetricsService::stop);
}
}
private static void checkCommand(
final ExportSubCommand exportSubCommand, final Long startBlock, final Long endBlock) {
checkNotNull(exportSubCommand.parentCommand);
checkNotNull(exportSubCommand.parentCommand.blockExporter);
checkNotNull(startBlock);
if (startBlock < 0) {
throw new CommandLine.ParameterException(
new CommandLine(exportSubCommand),
"--start-block must be greater than or equal to zero");
}
if (endBlock != null && startBlock >= endBlock) {
throw new CommandLine.ParameterException(
new CommandLine(exportSubCommand), "--end-block must be greater than --start-block");
}
}
private void outputBlock(final List<Block> blocks) {
if (blocksExportFile != null) {
final Path path = blocksExportFile.toPath();
try (final BufferedWriter fileWriter = Files.newBufferedWriter(path, UTF_8)) {
fileWriter.write(blocks.toString());
} catch (final IOException e) {
throw new ExecutionException(
new CommandLine(this), "An error occurred while trying to write the exported blocks");
}
} else {
parentCommand.out.println(blocks.toString());
}
}
}
private static Optional<MetricsService> initMetrics(final BlocksSubCommand parentCommand) {
Optional<MetricsService> metricsService = Optional.empty();
final MetricsConfiguration metricsConfiguration =
parentCommand.parentCommand.metricsConfiguration();
if (metricsConfiguration.isEnabled() || metricsConfiguration.isPushEnabled()) {
metricsService =
Optional.of(
MetricsService.create(
Vertx.vertx(),
metricsConfiguration,
parentCommand.parentCommand.getMetricsSystem()));
metricsService.ifPresent(MetricsService::start);
}
return metricsService;
}
}

@ -0,0 +1,88 @@
/*
* Copyright 2018 ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package tech.pegasys.pantheon.util;
import tech.pegasys.pantheon.controller.PantheonController;
import tech.pegasys.pantheon.ethereum.ProtocolContext;
import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain;
import tech.pegasys.pantheon.ethereum.core.Block;
import tech.pegasys.pantheon.ethereum.core.Hash;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import com.google.common.base.MoreObjects;
/** Pantheon Block Export Util. */
public class BlockExporter {
/**
* Export blocks that are stored in Pantheon's block storage.
*
* @param pantheonController the PantheonController that defines blockchain behavior
* @param <C> the consensus context type
* @param startBlock the starting index of the block list to export (inclusive)
* @param endBlock the ending index of the block list to export (exclusive), if not specified a
* single block will be export
* @return the export result
*/
public <C> ExportResult exportBlockchain(
final PantheonController<C> pantheonController, final Long startBlock, final Long endBlock) {
final ProtocolContext<C> context = pantheonController.getProtocolContext();
final MutableBlockchain blockchain = context.getBlockchain();
final Long sanitizedEndBlock = sanitizedEndBlockIndex(startBlock, endBlock);
final List<Block> blocks = new ArrayList<>();
for (long currentBlockIndex = startBlock;
currentBlockIndex < sanitizedEndBlock;
currentBlockIndex += 1) {
Optional<Hash> blockHashByNumber = blockchain.getBlockHashByNumber(currentBlockIndex);
blockHashByNumber.ifPresent(hash -> blocks.add(blockchain.getBlockByHash(hash)));
}
final boolean allBlocksAreFound = blocks.size() == (sanitizedEndBlock - startBlock);
return new ExportResult(blocks, allBlocksAreFound);
}
private Long sanitizedEndBlockIndex(final Long startBlock, final Long endBlock) {
if (endBlock == null) {
return startBlock + 1;
} else {
return endBlock;
}
}
public static final class ExportResult {
public final List<Block> blocks;
public final boolean allBlocksAreFound;
ExportResult(final List<Block> blocks, final boolean allBlocksAreFound) {
this.blocks = blocks;
this.allBlocksAreFound = allBlocksAreFound;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("blocks", blocks)
.add("allBlocksAreFound", allBlocksAreFound)
.toString();
}
}
}

@ -13,16 +13,42 @@
package tech.pegasys.pantheon.cli;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.contentOf;
import static org.assertj.core.util.Arrays.asList;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import tech.pegasys.pantheon.config.GenesisConfigFile;
import tech.pegasys.pantheon.controller.PantheonController;
import tech.pegasys.pantheon.crypto.SECP256K1;
import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain;
import tech.pegasys.pantheon.ethereum.core.Block;
import tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider;
import tech.pegasys.pantheon.ethereum.core.MiningParametersTestBuilder;
import tech.pegasys.pantheon.ethereum.core.PrivacyParameters;
import tech.pegasys.pantheon.ethereum.eth.EthProtocolConfiguration;
import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration;
import tech.pegasys.pantheon.ethereum.eth.transactions.TransactionPoolConfiguration;
import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem;
import tech.pegasys.pantheon.testutil.BlockTestUtil;
import tech.pegasys.pantheon.testutil.TestClock;
import tech.pegasys.pantheon.util.BlockExporter;
import tech.pegasys.pantheon.util.BlockImporter;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import picocli.CommandLine.Model.CommandSpec;
public class BlockSubCommandTest extends CommandTestAbstract {
@Rule public final TemporaryFolder folder = new TemporaryFolder();
private static final String EXPECTED_BLOCK_USAGE =
"Usage: pantheon blocks [-hV] [COMMAND]"
+ System.lineSeparator()
@ -49,8 +75,33 @@ public class BlockSubCommandTest extends CommandTestAbstract {
+ " -V, --version Print version information and exit."
+ System.lineSeparator();
private static final String EXPECTED_BLOCK_EXPORT_USAGE =
"Usage: pantheon blocks export [-hV] [--end-block=<LONG>] --start-block=<LONG>"
+ System.lineSeparator()
+ " [--to=<FILE>]"
+ System.lineSeparator()
+ "This command export a specific block from storage"
+ System.lineSeparator()
+ " --end-block=<LONG> the ending index of the block list to export"
+ System.lineSeparator()
+ " (exclusive), if not specified a single block will be"
+ System.lineSeparator()
+ " export"
+ System.lineSeparator()
+ " --start-block=<LONG> the starting index of the block list to export"
+ System.lineSeparator()
+ " (inclusive)"
+ System.lineSeparator()
+ " --to=<FILE> File to write the block list instead of standard output"
+ System.lineSeparator()
+ " -h, --help Show this help message and exit."
+ System.lineSeparator()
+ " -V, --version Print version information and exit."
+ System.lineSeparator();
private static final String BLOCK_SUBCOMMAND_NAME = "blocks";
private static final String BLOCK_IMPORT_SUBCOMMAND_NAME = "import";
private static final String BLOCK_EXPORT_SUBCOMMAND_NAME = "export";
// Block sub-command
@Test
@ -95,7 +146,7 @@ public class BlockSubCommandTest extends CommandTestAbstract {
@Test
public void callingBlockImportSubCommandWithPathMustImportBlocksWithThisPath() throws Exception {
File fileToImport = temp.newFile("blocks.file");
final File fileToImport = temp.newFile("blocks.file");
parseCommand(
BLOCK_SUBCOMMAND_NAME, BLOCK_IMPORT_SUBCOMMAND_NAME, "--from", fileToImport.getPath());
@ -106,4 +157,104 @@ public class BlockSubCommandTest extends CommandTestAbstract {
assertThat(commandOutput.toString()).isEmpty();
assertThat(commandErrorOutput.toString()).isEmpty();
}
// Export sub-sub-command
@Test
public void callingBlockExportSubCommandWithoutStartBlockMustDisplayErrorAndUsage() {
parseCommand(BLOCK_SUBCOMMAND_NAME, BLOCK_EXPORT_SUBCOMMAND_NAME);
final String expectedErrorOutputStart = "Missing required option '--start-block=<LONG>'";
assertThat(commandOutput.toString()).isEmpty();
assertThat(commandErrorOutput.toString()).startsWith(expectedErrorOutputStart);
}
@Test
public void callingBlockExportSubCommandHelpMustDisplayUsage() {
parseCommand(BLOCK_SUBCOMMAND_NAME, BLOCK_EXPORT_SUBCOMMAND_NAME, "--help");
assertThat(commandOutput.toString()).startsWith(EXPECTED_BLOCK_EXPORT_USAGE);
assertThat(commandErrorOutput.toString()).isEmpty();
}
@Test
public void callingBlockExportSubCommandWithNegativeStartBlockMustDisplayErrorAndUsage() {
final String expectedErrorOutputStart = "--start-block must be greater than or equal to zero";
parseCommand(BLOCK_SUBCOMMAND_NAME, BLOCK_EXPORT_SUBCOMMAND_NAME, "--start-block=-1");
assertThat(commandOutput.toString()).isEmpty();
assertThat(commandErrorOutput.toString()).startsWith(expectedErrorOutputStart);
}
@Test
public void
callingBlockExportSubCommandWithEndBlockGreaterThanStartBlockMustDisplayErrorMessage() {
final String expectedErrorOutputStart = "--end-block must be greater than --start-block";
parseCommand(
BLOCK_SUBCOMMAND_NAME, BLOCK_EXPORT_SUBCOMMAND_NAME, "--start-block=2", "--end-block=1");
assertThat(commandOutput.toString()).isEmpty();
assertThat(commandErrorOutput.toString()).startsWith(expectedErrorOutputStart);
}
@Test
public void callingBlockExportSubCommandWithEndBlockEqualToStartBlockMustDisplayErrorMessage() {
final String expectedErrorOutputStart = "--end-block must be greater than --start-block";
parseCommand(
BLOCK_SUBCOMMAND_NAME, BLOCK_EXPORT_SUBCOMMAND_NAME, "--start-block=2", "--end-block=2");
assertThat(commandOutput.toString()).isEmpty();
assertThat(commandErrorOutput.toString()).startsWith(expectedErrorOutputStart);
}
@Test
public void callingExportSubCommandWithFilePathMustWriteBlockInThisFile() throws Exception {
final long startBlock = 0L;
final long endBlock = 2L;
final PantheonController<?> pantheonController = initPantheonController();
final MutableBlockchain blockchain = pantheonController.getProtocolContext().getBlockchain();
final File outputFile = File.createTempFile("export", "store");
mockBlockExporter = new BlockExporter();
doReturn(pantheonController).when(mockControllerBuilder).build();
parseCommand(
BLOCK_SUBCOMMAND_NAME,
BLOCK_EXPORT_SUBCOMMAND_NAME,
"--start-block=" + startBlock,
"--end-block=" + endBlock,
"--to=" + outputFile.getPath());
final Block blockFromBlockchain =
blockchain.getBlockByHash(blockchain.getBlockHashByNumber(startBlock).get());
final Block secondBlockFromBlockchain =
blockchain.getBlockByHash(blockchain.getBlockHashByNumber(endBlock - 1).get());
assertThat(contentOf(outputFile))
.isEqualTo(asList(new Block[] {blockFromBlockchain, secondBlockFromBlockchain}).toString());
assertThat(commandOutput.toString()).isEmpty();
assertThat(commandErrorOutput.toString()).isEmpty();
}
private PantheonController<?> initPantheonController() throws IOException {
final BlockImporter blockImporter = new BlockImporter();
final Path dataDir = folder.newFolder().toPath();
final Path source = dataDir.resolve("1000.blocks");
BlockTestUtil.write1000Blocks(source);
final PantheonController<?> targetController =
new PantheonController.Builder()
.fromGenesisConfig(GenesisConfigFile.mainnet())
.synchronizerConfiguration(SynchronizerConfiguration.builder().build())
.ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig())
.storageProvider(new InMemoryStorageProvider())
.networkId(1)
.miningParameters(new MiningParametersTestBuilder().enabled(false).build())
.nodeKeys(SECP256K1.KeyPair.generate())
.metricsSystem(new NoOpMetricsSystem())
.privacyParameters(PrivacyParameters.DEFAULT)
.dataDirectory(dataDir)
.clock(TestClock.fixed())
.transactionPoolConfiguration(TransactionPoolConfiguration.builder().build())
.build();
blockImporter.importBlockchain(source, targetController);
return targetController;
}
}

@ -44,6 +44,7 @@ import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule;
import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration;
import tech.pegasys.pantheon.metrics.prometheus.MetricsConfiguration;
import tech.pegasys.pantheon.services.PantheonPluginContextImpl;
import tech.pegasys.pantheon.util.BlockExporter;
import tech.pegasys.pantheon.util.BlockImporter;
import tech.pegasys.pantheon.util.bytes.BytesValue;
@ -97,6 +98,8 @@ public abstract class CommandTestAbstract {
@Mock protected BlockBroadcaster mockBlockBroadcaster;
@Mock protected PantheonController<Object> mockController;
@Mock protected BlockImporter mockBlockImporter;
@Mock protected BlockExporter mockBlockExporter;
@Mock protected Logger mockLogger;
@Mock protected PantheonPluginContextImpl mockPantheonPluginContext;
@ -209,6 +212,7 @@ public abstract class CommandTestAbstract {
new TestPantheonCommand(
mockLogger,
mockBlockImporter,
mockBlockExporter,
mockRunnerBuilder,
mockControllerBuilderFactory,
keyLoader,
@ -237,6 +241,7 @@ public abstract class CommandTestAbstract {
TestPantheonCommand(
final Logger mockLogger,
final BlockImporter mockBlockImporter,
final BlockExporter mockBlockExporter,
final RunnerBuilder mockRunnerBuilder,
final PantheonController.Builder controllerBuilderFactory,
final KeyLoader keyLoader,
@ -245,6 +250,7 @@ public abstract class CommandTestAbstract {
super(
mockLogger,
mockBlockImporter,
mockBlockExporter,
mockRunnerBuilder,
controllerBuilderFactory,
pantheonPluginContext,

@ -0,0 +1,111 @@
/*
* Copyright 2018 ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package tech.pegasys.pantheon.util;
import static org.assertj.core.api.Assertions.assertThat;
import tech.pegasys.pantheon.config.GenesisConfigFile;
import tech.pegasys.pantheon.controller.PantheonController;
import tech.pegasys.pantheon.crypto.SECP256K1;
import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain;
import tech.pegasys.pantheon.ethereum.core.Block;
import tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider;
import tech.pegasys.pantheon.ethereum.core.MiningParametersTestBuilder;
import tech.pegasys.pantheon.ethereum.core.PrivacyParameters;
import tech.pegasys.pantheon.ethereum.eth.EthProtocolConfiguration;
import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration;
import tech.pegasys.pantheon.ethereum.eth.transactions.TransactionPoolConfiguration;
import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem;
import tech.pegasys.pantheon.testutil.BlockTestUtil;
import tech.pegasys.pantheon.testutil.TestClock;
import java.io.IOException;
import java.nio.file.Path;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
/** Tests for {@link BlockExporter}. */
public final class BlockExporterTest {
@ClassRule public static final TemporaryFolder folder = new TemporaryFolder();
private static PantheonController<?> targetController;
private final BlockExporter blockExporter = new BlockExporter();
@BeforeClass
public static void initPantheonController() throws IOException {
final BlockImporter blockImporter = new BlockImporter();
final Path dataDir = folder.newFolder().toPath();
final Path source = dataDir.resolve("1000.blocks");
BlockTestUtil.write1000Blocks(source);
targetController =
new PantheonController.Builder()
.fromGenesisConfig(GenesisConfigFile.mainnet())
.synchronizerConfiguration(SynchronizerConfiguration.builder().build())
.ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig())
.storageProvider(new InMemoryStorageProvider())
.networkId(1)
.miningParameters(new MiningParametersTestBuilder().enabled(false).build())
.nodeKeys(SECP256K1.KeyPair.generate())
.metricsSystem(new NoOpMetricsSystem())
.privacyParameters(PrivacyParameters.DEFAULT)
.dataDirectory(dataDir)
.clock(TestClock.fixed())
.transactionPoolConfiguration(TransactionPoolConfiguration.builder().build())
.build();
blockImporter.importBlockchain(source, targetController);
}
@Test
public void callingBlockExporterWithOnlyStartBlockShouldReturnOneBlock() throws Exception {
final long startBlock = 0L;
final MutableBlockchain blockchain = targetController.getProtocolContext().getBlockchain();
final Block blockFromBlockchain =
blockchain.getBlockByHash(blockchain.getBlockHashByNumber(startBlock).get());
BlockExporter.ExportResult exportResult =
blockExporter.exportBlockchain(targetController, startBlock, null);
assertThat(exportResult.blocks).contains(blockFromBlockchain);
}
@Test
public void callingBlockExporterWithStartBlockAndBlockShouldReturnSeveralBlocks()
throws Exception {
final long startBlock = 0L;
final long endBlock = 1L;
final MutableBlockchain blockchain = targetController.getProtocolContext().getBlockchain();
final Block blockFromBlockchain =
blockchain.getBlockByHash(blockchain.getBlockHashByNumber(startBlock).get());
final Block secondBlockFromBlockchain =
blockchain.getBlockByHash(blockchain.getBlockHashByNumber(endBlock - 1).get());
BlockExporter.ExportResult exportResult =
blockExporter.exportBlockchain(targetController, startBlock, endBlock);
assertThat(exportResult.blocks)
.contains(blockFromBlockchain)
.contains(secondBlockFromBlockchain);
}
}
Loading…
Cancel
Save