diff --git a/src/main/java/org/qora/block/BlockGenerator.java b/src/main/java/org/qora/block/BlockGenerator.java index f367ddd6..a9654cb3 100644 --- a/src/main/java/org/qora/block/BlockGenerator.java +++ b/src/main/java/org/qora/block/BlockGenerator.java @@ -66,6 +66,15 @@ public class BlockGenerator extends Thread { List newBlocks = new ArrayList<>(); while (running) { + // Sleep for a while + try { + repository.discardChanges(); // Free repository locks, if any + Thread.sleep(1000); // No point sleeping less than this as block timestamp millisecond values must be the same + } catch (InterruptedException e) { + // We've been interrupted - time to exit + return; + } + // Check blockchain hasn't changed BlockData lastBlockData = blockRepository.getLastBlock(); if (previousBlock == null || !Arrays.equals(previousBlock.getSignature(), lastBlockData.getSignature())) { @@ -155,15 +164,6 @@ public class BlockGenerator extends Thread { } finally { blockchainLock.unlock(); } - - // Sleep for a while - try { - repository.discardChanges(); // Free repository locks, if any - Thread.sleep(1000); // No point sleeping less than this as block timestamp millisecond values must be the same - } catch (InterruptedException e) { - // We've been interrupted - time to exit - return; - } } } catch (DataException e) { LOGGER.warn("Repository issue while running block generator", e); diff --git a/src/main/java/org/qora/controller/Synchronizer.java b/src/main/java/org/qora/controller/Synchronizer.java index cae32993..c6cb2b29 100644 --- a/src/main/java/org/qora/controller/Synchronizer.java +++ b/src/main/java/org/qora/controller/Synchronizer.java @@ -11,9 +11,12 @@ import org.qora.block.Block; import org.qora.block.Block.ValidationResult; import org.qora.block.GenesisBlock; import org.qora.data.block.BlockData; +import org.qora.data.network.BlockSummaryData; import org.qora.network.Peer; import org.qora.network.message.BlockMessage; +import org.qora.network.message.BlockSummariesMessage; import org.qora.network.message.GetBlockMessage; +import org.qora.network.message.GetBlockSummariesMessage; import org.qora.network.message.GetSignaturesMessage; import org.qora.network.message.Message; import org.qora.network.message.Message.MessageType; @@ -264,6 +267,18 @@ public class Synchronizer { return blockSignatures; } + private List getBlockSummaries(Peer peer, byte[] parentSignature, int numberRequested) { + Message getBlockSummariesMessage = new GetBlockSummariesMessage(parentSignature, numberRequested); + + Message message = peer.getResponse(getBlockSummariesMessage); + if (message == null || message.getType() != MessageType.BLOCK_SUMMARIES) + return null; + + BlockSummariesMessage blockSummariesMessage = (BlockSummariesMessage) message; + + return blockSummariesMessage.getBlockSummaries(); + } + private List getBlockSignatures(Peer peer, byte[] parentSignature, int numberRequested) { // TODO numberRequested is v2+ feature Message getSignaturesMessage = new GetSignaturesMessage(parentSignature); diff --git a/src/main/java/org/qora/data/network/BlockSummaryData.java b/src/main/java/org/qora/data/network/BlockSummaryData.java new file mode 100644 index 00000000..1bf35a03 --- /dev/null +++ b/src/main/java/org/qora/data/network/BlockSummaryData.java @@ -0,0 +1,37 @@ +package org.qora.data.network; + +import org.qora.data.block.BlockData; + +public class BlockSummaryData { + + // Properties + private int height; + private byte[] signature; + private byte[] generatorPublicKey; + + // Constructors + public BlockSummaryData(int height, byte[] signature, byte[] generatorPublicKey) { + this.height = height; + this.signature = signature; + this.generatorPublicKey = generatorPublicKey; + } + + public BlockSummaryData(BlockData blockData) { + this(blockData.getHeight(), blockData.getSignature(), blockData.getGeneratorPublicKey()); + } + + // Getters / setters + + public int getHeight() { + return this.height; + } + + public byte[] getSignature() { + return this.signature; + } + + public byte[] getGeneratorPublicKey() { + return this.generatorPublicKey; + } + +} diff --git a/src/main/java/org/qora/network/message/BlockSummariesMessage.java b/src/main/java/org/qora/network/message/BlockSummariesMessage.java new file mode 100644 index 00000000..ed79d2ab --- /dev/null +++ b/src/main/java/org/qora/network/message/BlockSummariesMessage.java @@ -0,0 +1,81 @@ +package org.qora.network.message; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.qora.data.block.BlockData; +import org.qora.data.network.BlockSummaryData; +import org.qora.transform.Transformer; +import org.qora.transform.block.BlockTransformer; + +import com.google.common.primitives.Ints; + +public class BlockSummariesMessage extends Message { + + private static final int BLOCK_SUMMARY_LENGTH = BlockTransformer.BLOCK_SIGNATURE_LENGTH + Transformer.INT_LENGTH + Transformer.PUBLIC_KEY_LENGTH; + + private List blockSummaries; + + public BlockSummariesMessage(List blocksData) { + // Extract what we need from block data + this(-1, blocksData.stream().map(blockData -> new BlockSummaryData(blockData)).collect(Collectors.toList())); + } + + private BlockSummariesMessage(int id, List blockSummaries) { + super(id, MessageType.BLOCK_SUMMARIES); + + this.blockSummaries = blockSummaries; + } + + public List getBlockSummaries() { + return this.blockSummaries; + } + + public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException { + int count = bytes.getInt(); + + if (bytes.remaining() != count * BLOCK_SUMMARY_LENGTH) + return null; + + List blockSummaries = new ArrayList<>(); + for (int i = 0; i < count; ++i) { + int height = bytes.getInt(); + + byte[] signature = new byte[BlockTransformer.BLOCK_SIGNATURE_LENGTH]; + bytes.get(signature); + + byte[] generatorPublicKey = new byte[Transformer.PUBLIC_KEY_LENGTH]; + bytes.get(generatorPublicKey); + + BlockSummaryData blockSummary = new BlockSummaryData(height, signature, generatorPublicKey); + blockSummaries.add(blockSummary); + } + + return new BlockSummariesMessage(id, blockSummaries); + } + + @Override + protected byte[] toData() { + try { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + bytes.write(Ints.toByteArray(this.blockSummaries.size())); + + for (BlockSummaryData blockSummary : this.blockSummaries) { + bytes.write(blockSummary.getHeight()); + bytes.write(blockSummary.getSignature()); + bytes.write(blockSummary.getGeneratorPublicKey()); + } + + return bytes.toByteArray(); + } catch (IOException e) { + return null; + } + } + +} diff --git a/src/main/java/org/qora/network/message/GetBlockSummariesMessage.java b/src/main/java/org/qora/network/message/GetBlockSummariesMessage.java new file mode 100644 index 00000000..057b9c83 --- /dev/null +++ b/src/main/java/org/qora/network/message/GetBlockSummariesMessage.java @@ -0,0 +1,64 @@ +package org.qora.network.message; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; + +import org.qora.transform.Transformer; +import org.qora.transform.block.BlockTransformer; + +public class GetBlockSummariesMessage extends Message { + + private static final int BLOCK_SIGNATURE_LENGTH = BlockTransformer.BLOCK_SIGNATURE_LENGTH; + + private byte[] parentSignature; + private int numberRequested; + + public GetBlockSummariesMessage(byte[] parentSignature, int numberRequested) { + this(-1, parentSignature, numberRequested); + } + + private GetBlockSummariesMessage(int id, byte[] parentSignature, int numberRequested) { + super(id, MessageType.GET_BLOCK_SUMMARIES); + + this.parentSignature = parentSignature; + this.numberRequested = numberRequested; + } + + public byte[] getParentSignature() { + return this.parentSignature; + } + + public int getNumberRequested() { + return this.numberRequested; + } + + public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException { + if (bytes.remaining() != BLOCK_SIGNATURE_LENGTH + Transformer.INT_LENGTH) + return null; + + byte[] parentSignature = new byte[BLOCK_SIGNATURE_LENGTH]; + bytes.get(parentSignature); + + int numberRequested = bytes.getInt(); + + return new GetBlockSummariesMessage(id, parentSignature, numberRequested); + } + + @Override + protected byte[] toData() { + try { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + bytes.write(this.parentSignature); + + bytes.write(this.numberRequested); + + return bytes.toByteArray(); + } catch (IOException e) { + return null; + } + } + +} diff --git a/src/main/java/org/qora/network/message/Message.java b/src/main/java/org/qora/network/message/Message.java index ab8eacf1..6b826d7c 100644 --- a/src/main/java/org/qora/network/message/Message.java +++ b/src/main/java/org/qora/network/message/Message.java @@ -40,7 +40,9 @@ public abstract class Message { VERSION(10), PEER_ID(11), PROOF(12), - PEERS_V2(13); + PEERS_V2(13), + GET_BLOCK_SUMMARIES(14), + BLOCK_SUMMARIES(15); public final int value; public final Method fromByteBuffer; diff --git a/src/main/java/org/qora/repository/BlockRepository.java b/src/main/java/org/qora/repository/BlockRepository.java index 2d622f40..4b0ddc6c 100644 --- a/src/main/java/org/qora/repository/BlockRepository.java +++ b/src/main/java/org/qora/repository/BlockRepository.java @@ -112,6 +112,11 @@ public interface BlockRepository { */ public List getBlocksWithGenerator(byte[] generatorPublicKey, Integer limit, Integer offset, Boolean reverse) throws DataException; + /** + * Returns blocks within height range. + */ + public List getBlocks(int firstBlockHeight, int lastBlockHeight) throws DataException; + /** * Saves block into repository. * diff --git a/src/main/java/org/qora/repository/hsqldb/HSQLDBBlockRepository.java b/src/main/java/org/qora/repository/hsqldb/HSQLDBBlockRepository.java index c28850e1..ae5c2af2 100644 --- a/src/main/java/org/qora/repository/hsqldb/HSQLDBBlockRepository.java +++ b/src/main/java/org/qora/repository/hsqldb/HSQLDBBlockRepository.java @@ -215,6 +215,26 @@ public class HSQLDBBlockRepository implements BlockRepository { } } + @Override + public List getBlocks(int firstBlockHeight, int lastBlockHeight) throws DataException { + String sql = "SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE height BETWEEN ? AND ?"; + + List blockData = new ArrayList<>(); + + try (ResultSet resultSet = this.repository.checkedExecute(sql, firstBlockHeight, lastBlockHeight)) { + if (resultSet == null) + return blockData; + + do { + blockData.add(getBlockFromResultSet(resultSet)); + } while (resultSet.next()); + + return blockData; + } catch (SQLException e) { + throw new DataException("Unable to fetch height-ranged blocks from repository", e); + } + } + @Override public void save(BlockData blockData) throws DataException { HSQLDBSaver saveHelper = new HSQLDBSaver("Blocks"); diff --git a/src/test/java/org/qora/test/TransactionTests.java b/src/test/java/org/qora/test/TransactionTests.java index 8724301a..cd193505 100644 --- a/src/test/java/org/qora/test/TransactionTests.java +++ b/src/test/java/org/qora/test/TransactionTests.java @@ -27,6 +27,7 @@ import org.qora.data.transaction.MultiPaymentTransactionData; import org.qora.data.transaction.PaymentTransactionData; import org.qora.data.transaction.RegisterNameTransactionData; import org.qora.data.transaction.SellNameTransactionData; +import org.qora.data.transaction.TransactionData; import org.qora.data.transaction.TransferAssetTransactionData; import org.qora.data.transaction.UpdateNameTransactionData; import org.qora.data.transaction.VoteOnPollTransactionData; @@ -156,9 +157,7 @@ public class TransactionTests extends Common { assertEquals(ValidationResult.OK, paymentTransaction.isValid()); // Forge new block with transaction - Block block = new Block(repository, parentBlockData, generator); - block.addTransaction(paymentTransactionData); - block.sign(); + Block block = forgeBlock(paymentTransactionData); assertTrue("Block signatures invalid", block.isSignatureValid()); assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid()); @@ -217,9 +216,7 @@ public class TransactionTests extends Common { assertEquals(ValidationResult.OK, registerNameTransaction.isValid()); // Forge new block with transaction - Block block = new Block(repository, parentBlockData, generator); - block.addTransaction(registerNameTransactionData); - block.sign(); + Block block = forgeBlock(registerNameTransactionData); assertTrue("Block signatures invalid", block.isSignatureValid()); assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid()); @@ -273,9 +270,7 @@ public class TransactionTests extends Common { assertEquals(ValidationResult.OK, updateNameTransaction.isValid()); // Forge new block with transaction - Block block = new Block(repository, parentBlockData, generator); - block.addTransaction(updateNameTransactionData); - block.sign(); + Block block = forgeBlock(updateNameTransactionData); assertTrue("Block signatures invalid", block.isSignatureValid()); assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid()); @@ -319,9 +314,7 @@ public class TransactionTests extends Common { assertEquals(ValidationResult.OK, sellNameTransaction.isValid()); // Forge new block with transaction - Block block = new Block(repository, parentBlockData, generator); - block.addTransaction(sellNameTransactionData); - block.sign(); + Block block = forgeBlock(sellNameTransactionData); assertTrue("Block signatures invalid", block.isSignatureValid()); assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid()); @@ -371,9 +364,7 @@ public class TransactionTests extends Common { assertEquals(ValidationResult.OK, cancelSellNameTransaction.isValid()); // Forge new block with transaction - Block block = new Block(repository, parentBlockData, generator); - block.addTransaction(cancelSellNameTransactionData); - block.sign(); + Block block = forgeBlock(cancelSellNameTransactionData); assertTrue("Block signatures invalid", block.isSignatureValid()); assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid()); @@ -418,9 +409,7 @@ public class TransactionTests extends Common { byte[] buyersReference = somePaymentTransaction.getTransactionData().getSignature(); // Forge new block with transaction - Block block = new Block(repository, parentBlockData, generator); - block.addTransaction(somePaymentTransaction.getTransactionData()); - block.sign(); + Block block = forgeBlock(somePaymentTransaction.getTransactionData()); block.process(); repository.saveChanges(); @@ -437,9 +426,7 @@ public class TransactionTests extends Common { assertEquals(ValidationResult.OK, buyNameTransaction.isValid()); // Forge new block with transaction - block = new Block(repository, parentBlockData, generator); - block.addTransaction(buyNameTransactionData); - block.sign(); + block = forgeBlock(buyNameTransactionData); assertTrue("Block signatures invalid", block.isSignatureValid()); assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid()); @@ -490,9 +477,7 @@ public class TransactionTests extends Common { assertEquals(ValidationResult.OK, createPollTransaction.isValid()); // Forge new block with transaction - Block block = new Block(repository, parentBlockData, generator); - block.addTransaction(createPollTransactionData); - block.sign(); + Block block = forgeBlock(createPollTransactionData); assertTrue("Block signatures invalid", block.isSignatureValid()); assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid()); @@ -549,9 +534,7 @@ public class TransactionTests extends Common { assertEquals(ValidationResult.OK, voteOnPollTransaction.isValid()); // Forge new block with transaction - Block block = new Block(repository, parentBlockData, generator); - block.addTransaction(voteOnPollTransactionData); - block.sign(); + Block block = forgeBlock(voteOnPollTransactionData); assertTrue("Block signatures invalid", block.isSignatureValid()); assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid()); @@ -617,9 +600,7 @@ public class TransactionTests extends Common { assertEquals(ValidationResult.OK, issueAssetTransaction.isValid()); // Forge new block with transaction - Block block = new Block(repository, parentBlockData, generator); - block.addTransaction(issueAssetTransactionData); - block.sign(); + Block block = forgeBlock(issueAssetTransactionData); assertTrue("Block signatures invalid", block.isSignatureValid()); assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid()); @@ -707,9 +688,7 @@ public class TransactionTests extends Common { assertEquals(ValidationResult.OK, transferAssetTransaction.isValid()); // Forge new block with transaction - Block block = new Block(repository, parentBlockData, generator); - block.addTransaction(transferAssetTransactionData); - block.sign(); + Block block = forgeBlock(transferAssetTransactionData); assertTrue("Block signatures invalid", block.isSignatureValid()); assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid()); @@ -787,9 +766,7 @@ public class TransactionTests extends Common { byte[] buyersReference = somePaymentTransaction.getTransactionData().getSignature(); // Forge new block with transaction - Block block = new Block(repository, parentBlockData, generator); - block.addTransaction(somePaymentTransaction.getTransactionData()); - block.sign(); + Block block = forgeBlock(somePaymentTransaction.getTransactionData()); block.process(); repository.saveChanges(); @@ -811,9 +788,7 @@ public class TransactionTests extends Common { assertEquals(ValidationResult.OK, createOrderTransaction.isValid()); // Forge new block with transaction - block = new Block(repository, parentBlockData, generator); - block.addTransaction(createOrderTransactionData); - block.sign(); + block = forgeBlock(createOrderTransactionData); assertTrue("Block signatures invalid", block.isSignatureValid()); assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid()); @@ -893,9 +868,7 @@ public class TransactionTests extends Common { assertEquals(ValidationResult.OK, cancelOrderTransaction.isValid()); // Forge new block with transaction - Block block = new Block(repository, parentBlockData, generator); - block.addTransaction(cancelOrderTransactionData); - block.sign(); + Block block = forgeBlock(cancelOrderTransactionData); assertTrue("Block signatures invalid", block.isSignatureValid()); assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid()); @@ -968,9 +941,7 @@ public class TransactionTests extends Common { assertEquals(ValidationResult.OK, createOrderTransaction.isValid()); // Forge new block with transaction - Block block = new Block(repository, parentBlockData, generator); - block.addTransaction(createOrderTransactionData); - block.sign(); + Block block = forgeBlock(createOrderTransactionData); assertTrue("Block signatures invalid", block.isSignatureValid()); assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid()); @@ -1078,9 +1049,7 @@ public class TransactionTests extends Common { assertEquals(ValidationResult.OK, multiPaymentTransaction.isValid()); // Forge new block with payment transaction - Block block = new Block(repository, parentBlockData, generator); - block.addTransaction(multiPaymentTransactionData); - block.sign(); + Block block = forgeBlock(multiPaymentTransactionData); assertTrue("Block signatures invalid", block.isSignatureValid()); assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid()); @@ -1148,9 +1117,7 @@ public class TransactionTests extends Common { assertEquals(ValidationResult.OK, messageTransaction.isValid()); // Forge new block with message transaction - Block block = new Block(repository, parentBlockData, generator); - block.addTransaction(messageTransactionData); - block.sign(); + Block block = forgeBlock(messageTransactionData); assertTrue("Block signatures invalid", block.isSignatureValid()); assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid()); @@ -1174,4 +1141,11 @@ public class TransactionTests extends Common { assertTrue("Recipient's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0); } + private Block forgeBlock(TransactionData transactionData) throws DataException { + Block block = new Block(repository, parentBlockData, generator); + block.addTransaction(transactionData); + block.sign(); + return block; + } + } \ No newline at end of file