package qora; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.logging.Logger; import network.Peer; import network.message.BlockMessage; import network.message.Message; import network.message.MessageFactory; import network.message.SignaturesMessage; import network.message.TransactionMessage; import qora.block.Block; import qora.transaction.Transaction; import database.DBSet; public class Synchronizer { private boolean run = true; public Synchronizer() { this.run = true; } public List<Transaction> synchronize(DBSet db, Block lastCommonBlock, List<Block> newBlocks) throws Exception { List<Transaction> orphanedTransactions = new ArrayList<Transaction>(); //VERIFY ALL BLOCKS TO PREVENT ORPHANING INCORRECTLY DBSet fork = db.fork(); //ORPHAN BLOCK IN FORK TO VALIDATE THE NEW BLOCKS if(lastCommonBlock != null) { //GET LAST BLOCK Block lastBlock = fork.getBlockMap().getLastBlock(); //ORPHAN LAST BLOCK UNTIL WE HAVE REACHED COMMON BLOCK while(!Arrays.equals(lastBlock.getSignature(), lastCommonBlock.getSignature())) { lastBlock.orphan(fork); lastBlock = fork.getBlockMap().getLastBlock(); } } //VALIDATE THE NEW BLOCKS for(Block block: newBlocks) { //CHECK IF VALID if(block.isValid(fork)) { //PROCESS TO VALIDATE NEXT BLOCKS block.process(fork); } else { //INVALID BLOCK THROW EXCEPTION throw new Exception("Dishonest peer"); } } //NEW BLOCKS ARE ALL VALID SO WE CAN ORPHAN THEM FOR REAL NOW if(lastCommonBlock != null) { //GET LAST BLOCK Block lastBlock = db.getBlockMap().getLastBlock(); //ORPHAN LAST BLOCK UNTIL WE HAVE REACHED COMMON BLOCK while(!Arrays.equals(lastBlock.getSignature(), lastCommonBlock.getSignature())) { //ADD ORPHANED TRANSACTIONS orphanedTransactions.addAll(lastBlock.getTransactions()); lastBlock.orphan(db); lastBlock = db.getBlockMap().getLastBlock(); } } //PROCESS THE NEW BLOCKS for(Block block: newBlocks) { //SYNCHRONIZED PROCESSING this.process(block); } return orphanedTransactions; } public void synchronize(Peer peer) throws Exception { Logger.getGlobal().info("Synchronizing: " + peer.getAddress().getHostAddress() + " - " + peer.getPing()); //FIND LAST COMMON BLOCK Block common = this.findLastCommonBlock(peer); //CHECK COMMON BLOCK EXISTS List<byte[]> signatures; if(Arrays.equals(common.getSignature(), DBSet.getInstance().getBlockMap().getLastBlockSignature())) { //GET NEXT 500 SIGNATURES signatures = this.getBlockSignatures(common, BlockChain.MAX_SIGNATURES, peer); //CREATE BLOCK BUFFER BlockBuffer blockBuffer = new BlockBuffer(signatures, peer); //GET AND PROCESS BLOCK BY BLOCK for(byte[] signature: signatures) { //GET BLOCK Block block = blockBuffer.getBlock(signature); //PROCESS BLOCK if(!this.process(block)) { //INVALID BLOCK THROW EXCEPTION throw new Exception("Dishonest peer"); } } //STOP BLOCKBUFFER blockBuffer.stopThread(); } else { //GET SIGNATURES FROM COMMON HEIGHT UNTIL CURRENT HEIGHT signatures = this.getBlockSignatures(common, DBSet.getInstance().getBlockMap().getLastBlock().getHeight() - common.getHeight(), peer); //GET THE BLOCKS FROM SIGNATURES List<Block> blocks = this.getBlocks(signatures, peer); //SYNCHRONIZE BLOCKS List<Transaction> orphanedTransactions = this.synchronize(DBSet.getInstance(), common, blocks); //SEND ORPHANED TRANSACTIONS TO PEER for(Transaction transaction: orphanedTransactions) { TransactionMessage transactionMessage = new TransactionMessage(transaction); peer.sendMessage(transactionMessage); } } } private List<byte[]> getBlockSignatures(Block start, int amount, Peer peer) throws Exception { //ASK NEXT 500 HEADERS SINCE START List<byte[]> headers = this.getBlockSignatures(start.getSignature(), peer); List<byte[]> nextHeaders; if(headers.size() > 0 && headers.size() < amount) { do { nextHeaders = this.getBlockSignatures(headers.get(headers.size()-1), peer); headers.addAll(nextHeaders); } while(headers.size() < amount && nextHeaders.size() > 0); } return headers; } private List<byte[]> getBlockSignatures(byte[] header, Peer peer) { ///CREATE MESSAGE Message message = MessageFactory.getInstance().createGetHeadersMessage(header); //SEND MESSAGE TO PEER SignaturesMessage response = (SignaturesMessage) peer.getResponse(message); return response.getSignatures(); } private Block findLastCommonBlock(Peer peer) throws Exception { Block block = DBSet.getInstance().getBlockMap().getLastBlock(); //GET HEADERS UNTIL COMMON BLOCK IS FOUND OR ALL BLOCKS HAVE BEEN CHECKED List<byte[]> headers = this.getBlockSignatures(block.getSignature(), peer); while(headers.size() == 0 && block.getHeight() > 1) { //GO 500 BLOCKS BACK for(int i=0; i<BlockChain.MAX_SIGNATURES && block.getHeight() > 1; i++) { block = block.getParent(); } headers = this.getBlockSignatures(block.getSignature(), peer); } //CHECK IF NO HEADERS FOUND EVEN AFTER CHECKING WITH THE GENESISBLOCK if(headers.size() == 0) { throw new Exception("Dishonest peer"); } //FIND LAST COMMON BLOCK IN HEADERS for(int i=headers.size()-1; i>=0; i--) { //CHECK IF WE KNOW BLOCK if(DBSet.getInstance().getBlockMap().contains(headers.get(i))) { return DBSet.getInstance().getBlockMap().get(headers.get(i)); } } return block; } private List<Block> getBlocks(List<byte[]> signatures, Peer peer) throws Exception { List<Block> blocks = new ArrayList<Block>(); for(byte[] signature: signatures) { //ADD TO LIST blocks.add(this.getBlock(signature, peer)); } return blocks; } private Block getBlock(byte[] signature, Peer peer) throws Exception { //CREATE MESSAGE Message message = MessageFactory.getInstance().createGetBlockMessage(signature); //SEND MESSAGE TO PEER BlockMessage response = (BlockMessage) peer.getResponse(message); //CHECK IF WE GOT RESPONSE if(response == null) { //ERROR throw new Exception("Peer timed out"); } //CHECK BLOCK SIGNATURE if(!response.getBlock().isSignatureValid()) { throw new Exception("Invalid block"); } //ADD TO LIST return response.getBlock(); } //SYNCHRONIZED DO NOT PROCCESS A BLOCK AT THE SAME TIME public synchronized boolean process(Block block) { //CHECK IF WE ARE STILL PROCESSING BLOCKS if(this.run) { //SYNCHRONIZED MIGHT HAVE BEEN PROCESSING PREVIOUS BLOCK if(block.isValid()) { //PROCESS DBSet.getInstance().getBlockMap().setProcessing(true); block.process(); DBSet.getInstance().getBlockMap().setProcessing(false); return true; } } return false; } public void stop() { this.run = false; this.process(null); } }