Implemented transactions record

This commit is contained in:
2021-08-13 17:34:19 +02:00
parent 415fda3d64
commit 4d94e3ad5b
12 changed files with 178 additions and 93 deletions

18
build.gradle Normal file
View File

@@ -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'
}

View File

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

View File

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

View File

@@ -15,7 +15,7 @@ public class Block implements Serializable {
private final int id;
private final int magicNumber;
private final float generationSecs;
private final List<Message> chatMessages;
private final List<Transaction> 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<Message> blockMessages
final List<Transaction> 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<Message> getMessages() {
return chatMessages;
public List<Transaction> getMessages() {
return transactions;
}
}

View File

@@ -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<Block> blockList = new LinkedList<>();
private final List<Message> incomingChatMessages = new ArrayList<>();
private final AtomicInteger lastMessageId = new AtomicInteger(1);
private final List<Transaction> incomingTransactions = new ArrayList<>();
private final AtomicInteger lastTransactionId = new AtomicInteger(1);
private final Map<Integer,Integer> minersCoins = new HashMap<>();
private final Map<String,Integer> 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<String, Integer> 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())
)
) {

View File

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

View File

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

View File

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

View File

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