From 4d94e3ad5b2dc8dcd259dcb6ced21ac21a00918a Mon Sep 17 00:00:00 2001 From: droideparanoico Date: Fri, 13 Aug 2021 17:34:19 +0200 Subject: [PATCH] Implemented transactions record --- build.gradle | 18 +++++ src/{ => main/java}/Main.java | 18 ++--- .../exceptions/NotEnoughCoinsException.java | 9 +++ src/{ => main/java}/model/Block.java | 15 ++-- src/{ => main/java}/model/BlockChain.java | 73 ++++++++++++++----- src/{ => main/java}/model/Miner.java | 0 .../java/model/Transaction.java} | 14 +++- src/{ => main/java}/util/FileManagement.java | 0 src/{ => main/java}/util/HashFunction.java | 0 src/{ => main/java}/util/Security.java | 4 +- src/main/java/util/TransactionsGenerator.java | 67 +++++++++++++++++ src/util/ChatClient.java | 53 -------------- 12 files changed, 178 insertions(+), 93 deletions(-) create mode 100644 build.gradle rename src/{ => main/java}/Main.java (69%) create mode 100644 src/main/java/exceptions/NotEnoughCoinsException.java rename src/{ => main/java}/model/Block.java (78%) rename src/{ => main/java}/model/BlockChain.java (67%) rename src/{ => main/java}/model/Miner.java (100%) rename src/{model/Message.java => main/java/model/Transaction.java} (61%) rename src/{ => main/java}/util/FileManagement.java (100%) rename src/{ => main/java}/util/HashFunction.java (100%) rename src/{ => main/java}/util/Security.java (97%) create mode 100644 src/main/java/util/TransactionsGenerator.java delete mode 100644 src/util/ChatClient.java diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..024034b --- /dev/null +++ b/build.gradle @@ -0,0 +1,18 @@ +apply plugin: 'idea' +apply plugin: 'java' + +repositories { + maven { + url 'https://repo.maven.apache.org/maven2' + name 'Maven Central' + } +} + +test { + useJUnitPlatform() +} + +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2' +} diff --git a/src/Main.java b/src/main/java/Main.java similarity index 69% rename from src/Main.java rename to src/main/java/Main.java index ea143ef..f4111ed 100644 --- a/src/Main.java +++ b/src/main/java/Main.java @@ -4,7 +4,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; import model.BlockChain; -import util.ChatClient; +import util.TransactionsGenerator; import model.Miner; import util.FileManagement; import util.Security; @@ -24,15 +24,15 @@ public final class Main { Security.generateKeys(); } - final var chatExecutor = Executors.newScheduledThreadPool(nThreads); + final var transactionsExecutor = Executors.newScheduledThreadPool(nThreads); - // Mocks a chat client who send messages to the blockchain - chatExecutor.scheduleAtFixedRate(new ChatClient(blockChain), 0, 200, TimeUnit.MILLISECONDS); + // Mocks a generator to send transactions into the blockchain + transactionsExecutor.scheduleAtFixedRate(new TransactionsGenerator(blockChain), 0, 200, TimeUnit.MILLISECONDS); final var minerExecutor = Executors.newFixedThreadPool(nThreads); - // Creation of 5 miners - IntStream.range(0, 5) + // Creation of 15 miners + IntStream.range(0, 15) .mapToObj(minerId -> new Miner(minerId, blockChain)) .forEach(minerExecutor::submit); @@ -42,10 +42,10 @@ public final class Main { minerExecutor.shutdownNow(); } - chatExecutor.shutdown(); + transactionsExecutor.shutdown(); - if (!chatExecutor.awaitTermination(60, TimeUnit.SECONDS)) { - chatExecutor.shutdownNow(); + if (!transactionsExecutor.awaitTermination(60, TimeUnit.SECONDS)) { + transactionsExecutor.shutdownNow(); } FileManagement.saveBlockChain(blockChain); diff --git a/src/main/java/exceptions/NotEnoughCoinsException.java b/src/main/java/exceptions/NotEnoughCoinsException.java new file mode 100644 index 0000000..21465e8 --- /dev/null +++ b/src/main/java/exceptions/NotEnoughCoinsException.java @@ -0,0 +1,9 @@ +package exceptions; + +public class NotEnoughCoinsException extends RuntimeException { + + public NotEnoughCoinsException(final String username) { + super(username + " has not enough coins to perform the transaction"); + } + +} diff --git a/src/model/Block.java b/src/main/java/model/Block.java similarity index 78% rename from src/model/Block.java rename to src/main/java/model/Block.java index b90370b..f6952a4 100644 --- a/src/model/Block.java +++ b/src/main/java/model/Block.java @@ -15,7 +15,7 @@ public class Block implements Serializable { private final int id; private final int magicNumber; private final float generationSecs; - private final List chatMessages; + private final List transactions; private final long timeStamp; public Block( @@ -25,7 +25,7 @@ public class Block implements Serializable { final int id, final int magicNumber, final float generationSecs, - final List blockMessages + final List blockTransactions ) { this.previousBlockHash = previousBlockHash; this.blockHash = blockHash; @@ -33,7 +33,7 @@ public class Block implements Serializable { this.id = id; this.magicNumber = magicNumber; this.generationSecs = generationSecs; - this.chatMessages = blockMessages; + this.transactions = blockTransactions; this.timeStamp = new Date().getTime(); } @@ -48,6 +48,7 @@ public class Block implements Serializable { public String toString() { return "Block: " + "\n" + "Created by miner #" + this.minerId + "\n" + + "miner #" + this.minerId + " gets 100 VC\n" + "Id: " + this.id + "\n" + "Timestamp: " + this.timeStamp + "\n" + "Magic number: " + this.magicNumber + "\n" @@ -59,13 +60,13 @@ public class Block implements Serializable { } public String messagesToString() { - return chatMessages.isEmpty() ? "Empty block\n" : chatMessages.stream() - .map(m -> String.valueOf(m.getId()).concat(" - ").concat(m.getText()).concat("\n")) + return transactions.isEmpty() ? "No transactions\n" : transactions.stream() + .map(m -> "ID: ".concat(String.valueOf(m.getId()).concat(" - ").concat(m.getText()).concat("\n"))) .collect(Collectors.joining()); } - public List getMessages() { - return chatMessages; + public List getMessages() { + return transactions; } } diff --git a/src/model/BlockChain.java b/src/main/java/model/BlockChain.java similarity index 67% rename from src/model/BlockChain.java rename to src/main/java/model/BlockChain.java index fa35715..7ab8465 100644 --- a/src/model/BlockChain.java +++ b/src/main/java/model/BlockChain.java @@ -10,6 +10,7 @@ import java.util.concurrent.atomic.AtomicInteger; import util.FileManagement; import util.HashFunction; import util.Security; +import exceptions.NotEnoughCoinsException; import java.util.*; import java.util.stream.Collectors; @@ -24,8 +25,10 @@ public class BlockChain implements Serializable { private final Random random = new Random(); private final List blockList = new LinkedList<>(); - private final List incomingChatMessages = new ArrayList<>(); - private final AtomicInteger lastMessageId = new AtomicInteger(1); + private final List incomingTransactions = new ArrayList<>(); + private final AtomicInteger lastTransactionId = new AtomicInteger(1); + private final Map minersCoins = new HashMap<>(); + private final Map usersCoins = new HashMap<>(); private int hashZeroes; private int magicNumber; @@ -50,7 +53,7 @@ public class BlockChain implements Serializable { final String previousBlockHash = (nextId > 0) ? blockList.get(nextId - 1).getBlockHash() : "0"; final String blockHash = calculateBlockHash(minerId); - incomingChatMessages.sort(Comparator.comparingInt(Message::getId)); + incomingTransactions.sort(Comparator.comparingInt(Transaction::getId)); final var block = new Block( previousBlockHash, @@ -59,11 +62,12 @@ public class BlockChain implements Serializable { nextId, magicNumber, generationSecs, - incomingChatMessages + incomingTransactions ); blockList.add(block); + minersCoins.put(minerId, 100); System.out.println(block); - incomingChatMessages.clear(); + incomingTransactions.clear(); if (generationSecs < LOWER_LIMIT_SECS) { hashZeroes += 1; @@ -77,19 +81,52 @@ public class BlockChain implements Serializable { } - public int getNextMessageId() { - return lastMessageId.getAndIncrement(); + public void acceptTransaction( + final int nextTransactionId, + final String sender, + final int amount, + final String receiver + ) throws + NoSuchAlgorithmException, + SignatureException, + InvalidKeyException, + IOException, + InvalidKeySpecException, + NotEnoughCoinsException + { + if (usersCoins.get(sender) <= 0 || usersCoins.get(sender) < amount) { + throw new NotEnoughCoinsException(sender); + } else { + transferCoins(sender, amount, receiver); + incomingTransactions.add(new Transaction( + nextTransactionId, + sender, + amount, + receiver, + Security.getPrivate(), + Security.getPublic() + )); + } } - public void acceptText(final int nextMessageId, final String name, final String text) - throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, IOException, InvalidKeySpecException { - incomingChatMessages.add(new Message( - nextMessageId, - name, - text, - Security.getPrivate(), - Security.getPublic() - )); + public void acceptUser(final String username, final int coins){ + usersCoins.put(username, coins); + } + + public void transferCoins(final String sender, final int amount, final String receiver) { + final int prevSenderCoins = usersCoins.get(sender); + final int prevReceiverCoins = usersCoins.get(receiver); + + usersCoins.put(sender, prevSenderCoins - amount); + usersCoins.put(receiver, prevReceiverCoins + amount); + } + + public Map getUsersCoins() { + return usersCoins; + } + + public int getNextTransactionId() { + return lastTransactionId.getAndIncrement(); } public boolean validateBlockchain() { @@ -109,13 +146,13 @@ public class BlockChain implements Serializable { if (!blockList.stream() .map(Block::getMessages) .flatMap(Collection::stream) - .map(Message::getId) + .map(Transaction::getId) .sorted() .collect(Collectors.toList()) .equals(blockList.stream() .map(Block::getMessages) .flatMap(Collection::stream) - .map(Message::getId) + .map(Transaction::getId) .collect(Collectors.toList()) ) ) { diff --git a/src/model/Miner.java b/src/main/java/model/Miner.java similarity index 100% rename from src/model/Miner.java rename to src/main/java/model/Miner.java diff --git a/src/model/Message.java b/src/main/java/model/Transaction.java similarity index 61% rename from src/model/Message.java rename to src/main/java/model/Transaction.java index ffe7b78..9419ae1 100644 --- a/src/model/Message.java +++ b/src/main/java/model/Transaction.java @@ -5,7 +5,7 @@ import util.Security; import java.io.Serializable; import java.security.*; -public class Message implements Serializable { +public class Transaction implements Serializable { private static final long serialVersionUID = 1L; @@ -14,10 +14,16 @@ public class Message implements Serializable { private final byte[] signature; private final PublicKey publicKey; - public Message(final int id, final String name, final String text, final PrivateKey privateKey, final PublicKey publicKey) - throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + public Transaction( + final int id, + final String sender, + final int amount, + final String receiver, + final PrivateKey privateKey, + final PublicKey publicKey + ) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { this.id = id; - this.text = "Chatter " + name + " says: " + text; + this.text = sender + " sent " + amount + " VC to " + receiver; this.signature = Security.sign(text, privateKey); this.publicKey = publicKey; } diff --git a/src/util/FileManagement.java b/src/main/java/util/FileManagement.java similarity index 100% rename from src/util/FileManagement.java rename to src/main/java/util/FileManagement.java diff --git a/src/util/HashFunction.java b/src/main/java/util/HashFunction.java similarity index 100% rename from src/util/HashFunction.java rename to src/main/java/util/HashFunction.java diff --git a/src/util/Security.java b/src/main/java/util/Security.java similarity index 97% rename from src/util/Security.java rename to src/main/java/util/Security.java index 2579d6a..9a6bde7 100644 --- a/src/util/Security.java +++ b/src/main/java/util/Security.java @@ -7,7 +7,7 @@ import java.security.*; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; -import model.Message; +import model.Transaction; public final class Security { @@ -42,7 +42,7 @@ public final class Security { return rsa.sign(); } - public static boolean messageIsValid(final Message msg) { + public static boolean messageIsValid(final Transaction msg) { return Security.verifySignature(msg.getId() + msg.getText(), msg.getSignature(), msg.getPublicKey()); } diff --git a/src/main/java/util/TransactionsGenerator.java b/src/main/java/util/TransactionsGenerator.java new file mode 100644 index 0000000..c19394d --- /dev/null +++ b/src/main/java/util/TransactionsGenerator.java @@ -0,0 +1,67 @@ +package util; + +import java.util.Arrays; +import java.util.stream.Stream; +import model.BlockChain; +import exceptions.NotEnoughCoinsException; + +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; +import java.security.spec.InvalidKeySpecException; +import java.util.Random; + +public class TransactionsGenerator implements Runnable { + + private static final Random random = new Random(); + private static final int INITIAL_COINS = 100; + private static final String[] userNames = Stream.of( + "Thom", "Jonny", "Ed", "Colin", "Phil" + ).toArray(String[]::new); + + private final BlockChain blockChain; + + public TransactionsGenerator(final BlockChain blockChain) { + this.blockChain = blockChain; + if (blockChain.getUsersCoins().isEmpty()) { + setInitialUserCoins(); + } + } + + @Override + public void run() { + try { + blockChain.acceptTransaction( + blockChain.getNextTransactionId(), + getRandomUserName(), + getRandomAmount(), + getRandomUserName() + ); + } catch (final NoSuchAlgorithmException | + SignatureException | + InvalidKeyException | + IOException | + InvalidKeySpecException | + NotEnoughCoinsException e) + { + e.printStackTrace(); + } + } + + private String getRandomUserName() { + return userNames[random.nextInt(userNames.length)]; + } + + private int getRandomAmount(){ + final int MIN_AMOUNT = 1; + final int MAX_AMOUNT = 100; + + return random.nextInt((MAX_AMOUNT - MIN_AMOUNT) + 1) + MIN_AMOUNT; + } + + private void setInitialUserCoins() { + Arrays.stream(userNames).forEach(userName -> blockChain.acceptUser(userName, INITIAL_COINS)); + } + +} \ No newline at end of file diff --git a/src/util/ChatClient.java b/src/util/ChatClient.java deleted file mode 100644 index ed71354..0000000 --- a/src/util/ChatClient.java +++ /dev/null @@ -1,53 +0,0 @@ -package util; - -import model.BlockChain; - -import java.io.IOException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.SignatureException; -import java.security.spec.InvalidKeySpecException; -import java.util.Random; - -public class ChatClient implements Runnable { - - private static final Random random = new Random(); - - private final BlockChain blockChain; - - public ChatClient(final BlockChain blockChain) { - this.blockChain = blockChain; - } - - @Override - public void run() { - try { - blockChain.acceptText(blockChain.getNextMessageId(), generateRandomName(), generateRandomText()); - } catch (final NoSuchAlgorithmException | SignatureException | InvalidKeyException | IOException | InvalidKeySpecException e) { - e.printStackTrace(); - } - } - - private String generateRandomName() { - final int leftLimit = 97; // letter 'a' - final int rightLimit = 122; // letter 'z' - final int targetStringLength = 5; - - return random.ints(leftLimit, rightLimit + 1) - .limit(targetStringLength) - .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) - .toString(); - } - - private String generateRandomText() { - final int leftLimit = 97; // letter 'a' - final int rightLimit = 122; // letter 'z' - final int targetStringLength = 20; - - return random.ints(leftLimit, rightLimit + 1) - .limit(targetStringLength) - .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) - .toString(); - } - -}