Implemented chat client simulation
This commit is contained in:
@@ -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();
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
31
src/model/ChatClient.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user