diff --git a/src/main/java/org/qortal/repository/BlockArchiveReader.java b/src/main/java/org/qortal/repository/BlockArchiveReader.java index 1b68a7c5..081917b2 100644 --- a/src/main/java/org/qortal/repository/BlockArchiveReader.java +++ b/src/main/java/org/qortal/repository/BlockArchiveReader.java @@ -105,6 +105,21 @@ public class BlockArchiveReader { return null; } + public List, List>> fetchBlocksFromRange( + int startHeight, int endHeight) { + + List, List>> blockInfoList = new ArrayList<>(); + + for (int height = startHeight; height <= endHeight; height++) { + Triple, List> blockInfo = this.fetchBlockAtHeight(height); + if (blockInfo == null) { + return blockInfoList; + } + blockInfoList.add(blockInfo); + } + return blockInfoList; + } + public Integer fetchHeightForSignature(byte[] signature, Repository repository) { // Lookup the height for the requested signature try { diff --git a/src/main/java/org/qortal/repository/BlockArchiveRepository.java b/src/main/java/org/qortal/repository/BlockArchiveRepository.java index c702a7ef..45465e93 100644 --- a/src/main/java/org/qortal/repository/BlockArchiveRepository.java +++ b/src/main/java/org/qortal/repository/BlockArchiveRepository.java @@ -36,6 +36,18 @@ public interface BlockArchiveRepository { */ public BlockData fromHeight(int height) throws DataException; + /** + * Returns a list of BlockData objects from archive using + * block height range. + * + * @param startHeight + * @return a list of BlockData objects, or an empty list if + * not found in blockchain. It is not guaranteed that all + * requested blocks will be returned. + * @throws DataException + */ + public List fromRange(int startHeight, int endHeight) throws DataException; + /** * Returns BlockData from archive using block reference. * Currently relies on a child block being the one block diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBBlockArchiveRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBBlockArchiveRepository.java index c491f862..32270213 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBBlockArchiveRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBBlockArchiveRepository.java @@ -3,6 +3,7 @@ package org.qortal.repository.hsqldb; import org.qortal.api.ApiError; import org.qortal.api.ApiExceptionFactory; import org.qortal.api.model.BlockSignerSummary; +import org.qortal.block.Block; import org.qortal.data.block.BlockArchiveData; import org.qortal.data.block.BlockData; import org.qortal.data.block.BlockSummaryData; @@ -53,6 +54,20 @@ public class HSQLDBBlockArchiveRepository implements BlockArchiveRepository { return null; } + @Override + public List fromRange(int startHeight, int endHeight) throws DataException { + List blocks = new ArrayList<>(); + + for (int height = startHeight; height < endHeight; height++) { + BlockData blockData = this.fromHeight(height); + if (blockData == null) { + return blocks; + } + blocks.add(blockData); + } + return blocks; + } + @Override public BlockData fromReference(byte[] reference) throws DataException { BlockData referenceBlock = this.repository.getBlockArchiveRepository().fromSignature(reference); diff --git a/src/main/java/org/qortal/utils/BlockArchiveUtils.java b/src/main/java/org/qortal/utils/BlockArchiveUtils.java new file mode 100644 index 00000000..0beff026 --- /dev/null +++ b/src/main/java/org/qortal/utils/BlockArchiveUtils.java @@ -0,0 +1,78 @@ +package org.qortal.utils; + +import org.qortal.data.at.ATStateData; +import org.qortal.data.block.BlockData; +import org.qortal.data.transaction.TransactionData; +import org.qortal.repository.BlockArchiveReader; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; + +import java.util.List; + +public class BlockArchiveUtils { + + /** + * importFromArchive + *

+ * Reads the requested block range from the archive + * and imports the BlockData and AT state data hashes + * This can be used to convert a block archive back + * into the HSQLDB, in order to make it SQL-compatible + * again. + *

+ * Note: calls discardChanges() and saveChanges(), so + * make sure that you commit any existing repository + * changes before calling this method. + * + * @param startHeight The earliest block to import + * @param endHeight The latest block to import + * @param repository A clean repository session + * @throws DataException + */ + public static void importFromArchive(int startHeight, int endHeight, Repository repository) throws DataException { + repository.discardChanges(); + final int requestedRange = endHeight+1-startHeight; + + List, List>> blockInfoList = + BlockArchiveReader.getInstance().fetchBlocksFromRange(startHeight, endHeight); + + // Ensure that we have received all of the requested blocks + if (blockInfoList == null || blockInfoList.isEmpty()) { + throw new IllegalStateException("No blocks found when importing from archive"); + } + if (blockInfoList.size() != requestedRange) { + throw new IllegalStateException("Non matching block count when importing from archive"); + } + Triple, List> firstBlock = blockInfoList.get(0); + if (firstBlock == null || firstBlock.getA().getHeight() != startHeight) { + throw new IllegalStateException("Non matching first block when importing from archive"); + } + if (blockInfoList.size() > 0) { + Triple, List> lastBlock = + blockInfoList.get(blockInfoList.size() - 1); + if (lastBlock == null || lastBlock.getA().getHeight() != endHeight) { + throw new IllegalStateException("Non matching last block when importing from archive"); + } + } + + // Everything seems okay, so go ahead with the import + for (Triple, List> blockInfo : blockInfoList) { + try { + // Save block + repository.getBlockRepository().save(blockInfo.getA()); + + // Save AT state data hashes + for (ATStateData atStateData : blockInfo.getC()) { + atStateData.setHeight(blockInfo.getA().getHeight()); + repository.getATRepository().save(atStateData); + } + + } catch (DataException e) { + repository.discardChanges(); + throw new IllegalStateException("Unable to import blocks from archive"); + } + } + repository.saveChanges(); + } + +}