Implemented chat client simulation

This commit is contained in:
2021-07-31 14:00:09 +02:00
parent d433dbf721
commit a1db603c7f
4 changed files with 152 additions and 73 deletions

View File

@@ -2,25 +2,40 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import model.BlockChain; import model.BlockChain;
import model.ChatClient;
import model.Miner; import model.Miner;
public final class Main { public final class Main {
public static void main(final String[] args) throws InterruptedException { public static void main(final String[] args) throws InterruptedException {
final var nThreads = Runtime.getRuntime().availableProcessors();
final var blockChain = new BlockChain(); final var blockChain = new BlockChain();
blockChain.load(); blockChain.load();
final var nThreads = Runtime.getRuntime().availableProcessors(); final var chatExecutor = Executors.newScheduledThreadPool(nThreads);
final var executor = Executors.newFixedThreadPool(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) IntStream.range(0, 5)
.mapToObj(minerId -> new Miner(minerId, blockChain)) .mapToObj(minerId -> new Miner(minerId, blockChain))
.forEach(executor::submit); .forEach(minerExecutor::submit);
executor.shutdown(); minerExecutor.shutdown();
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { if (!minerExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow(); minerExecutor.shutdownNow();
}
chatExecutor.shutdown();
if (!chatExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
chatExecutor.shutdownNow();
} }
blockChain.save(); blockChain.save();

View File

@@ -1,32 +1,39 @@
package model; package model;
import static java.lang.String.valueOf;
import util.HashFunction;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
import java.util.Random; import java.util.List;
import java.util.stream.Collectors;
public class Block implements Serializable { public class Block implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private final String previousBlockHash; private final String previousBlockHash;
private final String blockHash;
private final int minerId; private final int minerId;
private final int id; private final int id;
private final int hashZeroes; private final int magicNumber;
private final float generationSecs;
private final List<String> chatMessages;
private final long timeStamp; 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<String> chatMessages
) {
this.previousBlockHash = previousBlockHash; this.previousBlockHash = previousBlockHash;
this.blockHash = blockHash;
this.minerId = minerId; this.minerId = minerId;
this.id = id; this.id = id;
this.hashZeroes = hashZeroes; this.magicNumber = magicNumber;
this.generationSecs = generationSecs;
this.chatMessages = chatMessages;
this.timeStamp = new Date().getTime(); this.timeStamp = new Date().getTime();
this.blockHash = calculateBlockHash();
} }
public String getBlockHash() { public String getBlockHash() {
@@ -37,35 +44,23 @@ public class Block implements Serializable {
return previousBlockHash; 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() { public String toString() {
return "Block: " + "\n" return "Block: " + "\n"
+ "Created by miner #" + this.minerId + "\n" + "Created by miner #" + this.minerId + "\n"
+ "Id: " + this.id + "\n" + "Id: " + this.id + "\n"
+ "Timestamp: " + this.timeStamp + "\n" + "Timestamp: " + this.timeStamp + "\n"
+ "Magic number: " + this.magicNumber + "\n" + "Magic number: " + this.magicNumber + "\n"
+ "Hash of the previous block: " + "\n" + this.previousBlockHash + "\n" + "Hash of the previous block: " + "\n" + this.previousBlockHash + "\n"
+ "Hash of the block: \n" + this.blockHash + "\n" + "Hash of the block: \n" + this.blockHash + "\n"
+ "Block was generating for " + this.generationSecs + " seconds"; + "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());
} }
} }

View File

@@ -1,46 +1,80 @@
package model; package model;
import util.FileManagement; import util.FileManagement;
import java.util.LinkedList; import util.HashFunction;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static java.lang.String.valueOf;
public class BlockChain { public class BlockChain {
private final Random random = new Random();
private final List<Block> blockList = new LinkedList<>();
private final List<String> chatMessages = new ArrayList<>();
private int hashZeroes; private int hashZeroes;
private static final List<Block> blockList = new LinkedList<>(); private int magicNumber;
private float generationSecs = 0;
public synchronized void addBlock(final int minerId) { public synchronized void addBlock(final int minerId) {
final int nextId = blockList.size(); final int nextId = blockList.size();
final String previousBlockHash = (nextId > 0) ? blockList.get(nextId - 1).getBlockHash() : "0"; 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); blockList.add(block);
System.out.println(block);
this.chatMessages.clear();
final float generationTime = blockList.get(nextId).getGenerationSecs(); if (generationSecs < 1) {
final String CASE;
if (generationTime < 10) {
hashZeroes += 1; hashZeroes += 1;
CASE = "N was increased to " + hashZeroes + "\n"; System.out.println("N was increased to " + hashZeroes +"\n");
} else if (generationTime > 60) { } else if (generationSecs > 10) {
hashZeroes -= 1; hashZeroes -= 1;
CASE = "N was decreased by 1" + "\n"; System.out.println("N was decreased by 1\n");
} else { } 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()) { if (blockList.isEmpty()) {
return true; return true;
} }
final List<String> previousHashes = blockList.stream().map(Block::getPreviousBlockHash).collect( final List<String> previousHashes = blockList.stream().map(Block::getPreviousBlockHash).collect(
Collectors.toList()); Collectors.toList());
final List<String> hashes = blockList.stream().map(Block::getBlockHash).collect(Collectors.toList()); final List<String> hashes = blockList.stream().map(Block::getBlockHash).collect(Collectors.toList());
for (var index = 1; index < hashes.size(); index++) { for (var index = 1; index < hashes.size(); index++) {
@@ -52,19 +86,23 @@ public class BlockChain {
return true; return true;
} }
public String toString() { private String calculateBlockHash(final int minerId) {
if (!validateBlockchain()) { final long start = System.currentTimeMillis();
System.out.println("Blockchain invalid!"); var hash = "";
} do {
return blockList.stream().map(Block::toString).collect(Collectors.joining("\n")); 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() { private int calculateMagicNumber() {
FileManagement.loadBlockchain(blockList); magicNumber = random.nextInt();
} return magicNumber;
public void save() {
FileManagement.saveBlockchain(blockList);
} }
} }

31
src/model/ChatClient.java Normal file
View File

@@ -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();
}
}