diff --git a/src/Main.java b/src/Main.java index 28c7755..906ef06 100644 --- a/src/Main.java +++ b/src/Main.java @@ -2,25 +2,40 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; import model.BlockChain; +import model.ChatClient; import model.Miner; public final class Main { public static void main(final String[] args) throws InterruptedException { + final var nThreads = Runtime.getRuntime().availableProcessors(); final var blockChain = new BlockChain(); blockChain.load(); - final var nThreads = Runtime.getRuntime().availableProcessors(); - final var executor = Executors.newFixedThreadPool(nThreads); + final var chatExecutor = Executors.newScheduledThreadPool(nThreads); + // Mocks 3 chat clients who send messages to the blockchain + IntStream.range(0, 3) + .mapToObj(chatClientId -> new ChatClient(chatClientId, blockChain)) + .forEach(e -> chatExecutor.scheduleAtFixedRate(e, 0, 100, TimeUnit.MILLISECONDS)); + + final var minerExecutor = Executors.newFixedThreadPool(nThreads); + + // Creation of 5 miners IntStream.range(0, 5) - .mapToObj(minerId -> new Miner(minerId, blockChain)) - .forEach(executor::submit); + .mapToObj(minerId -> new Miner(minerId, blockChain)) + .forEach(minerExecutor::submit); - executor.shutdown(); + minerExecutor.shutdown(); - if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { - executor.shutdownNow(); + if (!minerExecutor.awaitTermination(60, TimeUnit.SECONDS)) { + minerExecutor.shutdownNow(); + } + + chatExecutor.shutdown(); + + if (!chatExecutor.awaitTermination(60, TimeUnit.SECONDS)) { + chatExecutor.shutdownNow(); } blockChain.save(); diff --git a/src/model/Block.java b/src/model/Block.java index d8ac74a..12d39f7 100644 --- a/src/model/Block.java +++ b/src/model/Block.java @@ -1,32 +1,39 @@ package model; -import static java.lang.String.valueOf; - -import util.HashFunction; import java.io.Serializable; import java.util.Date; -import java.util.Random; +import java.util.List; +import java.util.stream.Collectors; public class Block implements Serializable { private static final long serialVersionUID = 1L; private final String previousBlockHash; + private final String blockHash; private final int minerId; private final int id; - private final int hashZeroes; + private final int magicNumber; + private final float generationSecs; + private final List chatMessages; private final long timeStamp; - private final String blockHash; - private final transient Random random = new Random(); - private int magicNumber; - private float generationSecs = 0; - public Block(final String previousBlockHash,final int minerId, final int id, final int hashZeroes) { + public Block( + final String previousBlockHash, + final String blockHash, + final int minerId, + final int id, + final int magicNumber, + final float generationSecs, + final List chatMessages + ) { this.previousBlockHash = previousBlockHash; + this.blockHash = blockHash; this.minerId = minerId; this.id = id; - this.hashZeroes = hashZeroes; + this.magicNumber = magicNumber; + this.generationSecs = generationSecs; + this.chatMessages = chatMessages; this.timeStamp = new Date().getTime(); - this.blockHash = calculateBlockHash(); } public String getBlockHash() { @@ -37,35 +44,23 @@ public class Block implements Serializable { return previousBlockHash; } - public float getGenerationSecs() { - return generationSecs; - } - - private String calculateBlockHash() { - final long start = System.currentTimeMillis(); - var hash = ""; - do { - hash = HashFunction.applySha256(this.id + valueOf(this.timeStamp) + calculateMagicNumber()); - } while (!hash.matches("(?s)0{" + hashZeroes + "}([^0].*)?")); - final long end = System.currentTimeMillis(); - generationSecs = (end - start) / 1000F; - return hash; - } - - private int calculateMagicNumber() { - magicNumber = random.nextInt(); - return magicNumber; - } - public String toString() { return "Block: " + "\n" - + "Created by miner #" + this.minerId + "\n" - + "Id: " + this.id + "\n" - + "Timestamp: " + this.timeStamp + "\n" - + "Magic number: " + this.magicNumber + "\n" - + "Hash of the previous block: " + "\n" + this.previousBlockHash + "\n" - + "Hash of the block: \n" + this.blockHash + "\n" - + "Block was generating for " + this.generationSecs + " seconds"; + + "Created by miner #" + this.minerId + "\n" + + "Id: " + this.id + "\n" + + "Timestamp: " + this.timeStamp + "\n" + + "Magic number: " + this.magicNumber + "\n" + + "Hash of the previous block: " + "\n" + this.previousBlockHash + "\n" + + "Hash of the block: \n" + this.blockHash + "\n" + + "Block data: \n" + + getMessages() + + "Block was generating for " + this.generationSecs + " seconds"; + } + + public String getMessages() { + return chatMessages.stream() + .map(m -> m.concat("\n")) + .collect(Collectors.joining()); } } diff --git a/src/model/BlockChain.java b/src/model/BlockChain.java index a0ea044..a100662 100644 --- a/src/model/BlockChain.java +++ b/src/model/BlockChain.java @@ -1,46 +1,80 @@ package model; import util.FileManagement; -import java.util.LinkedList; -import java.util.List; +import util.HashFunction; + +import java.util.*; import java.util.stream.Collectors; +import static java.lang.String.valueOf; + public class BlockChain { + private final Random random = new Random(); + private final List blockList = new LinkedList<>(); + private final List chatMessages = new ArrayList<>(); private int hashZeroes; - private static final List blockList = new LinkedList<>(); + private int magicNumber; + private float generationSecs = 0; public synchronized void addBlock(final int minerId) { final int nextId = blockList.size(); final String previousBlockHash = (nextId > 0) ? blockList.get(nextId - 1).getBlockHash() : "0"; + final String blockHash = calculateBlockHash(minerId); - final var block = new Block(previousBlockHash, minerId, nextId, hashZeroes); + final var block = new Block( + previousBlockHash, + blockHash, + minerId, + nextId, + magicNumber, + generationSecs, + chatMessages + ); blockList.add(block); + System.out.println(block); + this.chatMessages.clear(); - final float generationTime = blockList.get(nextId).getGenerationSecs(); - final String CASE; - - if (generationTime < 10) { + if (generationSecs < 1) { hashZeroes += 1; - CASE = "N was increased to " + hashZeroes + "\n"; - } else if (generationTime > 60) { + System.out.println("N was increased to " + hashZeroes +"\n"); + } else if (generationSecs > 10) { hashZeroes -= 1; - CASE = "N was decreased by 1" + "\n"; + System.out.println("N was decreased by 1\n"); } else { - CASE = "N stays the same"; + System.out.println("N stays the same\n"); } - System.out.println(block); - System.out.println(CASE); } - public boolean validateBlockchain() { + public void acceptMessage(String message) { + if (!blockList.isEmpty()) { + chatMessages.add(message); + } + } + + public void load() { + FileManagement.loadBlockchain(blockList); + } + + public void save() { + FileManagement.saveBlockchain(blockList); + } + + public String toString() { + if (!validateBlockchain()) { + System.out.println("Blockchain invalid!"); + } + return blockList.stream().map(Block::toString).collect(Collectors.joining("\n")); + } + + private boolean validateBlockchain() { if (blockList.isEmpty()) { return true; } final List previousHashes = blockList.stream().map(Block::getPreviousBlockHash).collect( - Collectors.toList()); + Collectors.toList()); final List hashes = blockList.stream().map(Block::getBlockHash).collect(Collectors.toList()); for (var index = 1; index < hashes.size(); index++) { @@ -52,19 +86,23 @@ public class BlockChain { return true; } - public String toString() { - if (!validateBlockchain()) { - System.out.println("Blockchain invalid!"); - } - return blockList.stream().map(Block::toString).collect(Collectors.joining("\n")); + private String calculateBlockHash(final int minerId) { + final long start = System.currentTimeMillis(); + var hash = ""; + do { + hash = HashFunction.applySha256(minerId + + valueOf(new Date().getTime()) + + calculateMagicNumber() + ); + } while (!hash.matches("(?s)0{" + hashZeroes + "}([^0].*)?")); + final long end = System.currentTimeMillis(); + generationSecs = (end - start) / 1000F; + return hash; } - public void load() { - FileManagement.loadBlockchain(blockList); - } - - public void save() { - FileManagement.saveBlockchain(blockList); + private int calculateMagicNumber() { + magicNumber = random.nextInt(); + return magicNumber; } } diff --git a/src/model/ChatClient.java b/src/model/ChatClient.java new file mode 100644 index 0000000..94ebf5a --- /dev/null +++ b/src/model/ChatClient.java @@ -0,0 +1,31 @@ +package model; + +import java.util.Random; + +public class ChatClient implements Runnable { + + private final int clientId; + private final BlockChain blockChain; + private static final Random random = new Random(); + + public ChatClient(int chatClientId, BlockChain blockChain) { + this.clientId = chatClientId; + this.blockChain = blockChain; + } + + @Override + public void run() { + blockChain.acceptMessage(generateRandomAlphabeticString()); + } + + private String generateRandomAlphabeticString() { + int leftLimit = 97; // letter 'a' + int rightLimit = 122; // letter 'z' + int targetStringLength = 10; + + return "Client " + clientId + " says: " + random.ints(leftLimit, rightLimit + 1) + .limit(targetStringLength) + .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) + .toString(); + } +} \ No newline at end of file