mirror of
https://github.com/Qortal/qortal.git
synced 2025-03-30 09:05:52 +00:00
130 lines
4.3 KiB
Java
130 lines
4.3 KiB
Java
package qora.block;
|
|
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
|
|
import org.apache.logging.log4j.LogManager;
|
|
import org.apache.logging.log4j.Logger;
|
|
|
|
import data.block.BlockData;
|
|
import data.transaction.TransactionData;
|
|
import qora.account.PrivateKeyAccount;
|
|
import qora.block.Block.ValidationResult;
|
|
import qora.transaction.Transaction;
|
|
import repository.BlockRepository;
|
|
import repository.DataException;
|
|
import repository.Repository;
|
|
import repository.RepositoryManager;
|
|
|
|
// Forging new blocks
|
|
|
|
// How is the private key going to be supplied?
|
|
|
|
public class BlockGenerator extends Thread {
|
|
|
|
// Properties
|
|
private byte[] generatorPrivateKey;
|
|
private PrivateKeyAccount generator;
|
|
private Block previousBlock;
|
|
private Block newBlock;
|
|
private boolean running;
|
|
|
|
// Other properties
|
|
private static final Logger LOGGER = LogManager.getLogger(BlockGenerator.class);
|
|
|
|
// Constructors
|
|
|
|
public BlockGenerator(byte[] generatorPrivateKey) {
|
|
this.generatorPrivateKey = generatorPrivateKey;
|
|
this.previousBlock = null;
|
|
this.newBlock = null;
|
|
this.running = true;
|
|
}
|
|
|
|
// Main thread loop
|
|
@Override
|
|
public void run() {
|
|
Thread.currentThread().setName("BlockGenerator");
|
|
|
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
generator = new PrivateKeyAccount(repository, generatorPrivateKey);
|
|
|
|
// Going to need this a lot...
|
|
BlockRepository blockRepository = repository.getBlockRepository();
|
|
|
|
while (running) {
|
|
// Check blockchain hasn't changed
|
|
BlockData lastBlockData = blockRepository.getLastBlock();
|
|
if (previousBlock == null || !Arrays.equals(previousBlock.getSignature(), lastBlockData.getSignature())) {
|
|
previousBlock = new Block(repository, lastBlockData);
|
|
newBlock = null;
|
|
}
|
|
|
|
// Do we need to build a potential new block?
|
|
if (newBlock == null)
|
|
newBlock = new Block(repository, previousBlock.getBlockData(), generator);
|
|
|
|
// Is new block valid yet? (Before adding unconfirmed transactions)
|
|
if (newBlock.isValid() == ValidationResult.OK) {
|
|
// Add unconfirmed transactions
|
|
addUnconfirmedTransactions(repository, newBlock);
|
|
|
|
// Sign to create block's signature
|
|
newBlock.sign();
|
|
|
|
// If newBlock is still valid then we can use it
|
|
ValidationResult validationResult = newBlock.isValid();
|
|
if (validationResult == ValidationResult.OK) {
|
|
// Add to blockchain - something else will notice and broadcast new block to network
|
|
try {
|
|
newBlock.process();
|
|
LOGGER.info("Generated new block: " + newBlock.getBlockData().getHeight());
|
|
repository.saveChanges();
|
|
} catch (DataException e) {
|
|
// Unable to process block - report and discard
|
|
LOGGER.error("Unable to process newly generated block?", e);
|
|
newBlock = null;
|
|
}
|
|
} else {
|
|
// No longer valid? Report and discard
|
|
LOGGER.error("Valid, generated block now invalid '" + validationResult.name() + "' after adding unconfirmed transactions?");
|
|
newBlock = null;
|
|
}
|
|
}
|
|
|
|
// Sleep for a while
|
|
try {
|
|
Thread.sleep(1000);
|
|
} catch (InterruptedException e) {
|
|
// We've been interrupted - time to exit
|
|
return;
|
|
}
|
|
}
|
|
} catch (DataException e) {
|
|
LOGGER.warn("Repository issue while running block generator", e);
|
|
}
|
|
}
|
|
|
|
private void addUnconfirmedTransactions(Repository repository, Block newBlock) throws DataException {
|
|
// Grab all unconfirmed transactions (already sorted)
|
|
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getAllUnconfirmedTransactions();
|
|
|
|
// Remove transactions that have timestamp later than block's timestamp (not yet valid)
|
|
unconfirmedTransactions.removeIf(transactionData -> transactionData.getTimestamp() > newBlock.getBlockData().getTimestamp());
|
|
// Remove transactions that have expired deadline for this block
|
|
unconfirmedTransactions.removeIf(transactionData -> Transaction.fromData(repository, transactionData).getDeadline() <= newBlock.getBlockData().getTimestamp());
|
|
|
|
// Attempt to add transactions until block is full, or we run out
|
|
for (TransactionData transactionData : unconfirmedTransactions)
|
|
if (!newBlock.addTransaction(transactionData))
|
|
break;
|
|
}
|
|
|
|
public void shutdown() {
|
|
this.running = false;
|
|
// Interrupt too, absorbed by HSQLDB but could be caught by Thread.sleep()
|
|
this.interrupt();
|
|
}
|
|
|
|
}
|