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.concurrent.TimeUnit;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import model.BlockChain; import model.BlockChain;
import util.ChatClient; import util.TransactionsGenerator;
import model.Miner; import model.Miner;
import util.FileManagement; import util.FileManagement;
import util.Security; import util.Security;
@@ -24,15 +24,15 @@ public final class Main {
Security.generateKeys(); Security.generateKeys();
} }
final var chatExecutor = Executors.newScheduledThreadPool(nThreads); final var transactionsExecutor = Executors.newScheduledThreadPool(nThreads);
// Mocks a chat client who send messages to the blockchain // Mocks a generator to send transactions into the blockchain
chatExecutor.scheduleAtFixedRate(new ChatClient(blockChain), 0, 200, TimeUnit.MILLISECONDS); transactionsExecutor.scheduleAtFixedRate(new TransactionsGenerator(blockChain), 0, 200, TimeUnit.MILLISECONDS);
final var minerExecutor = Executors.newFixedThreadPool(nThreads); final var minerExecutor = Executors.newFixedThreadPool(nThreads);
// Creation of 5 miners // Creation of 15 miners
IntStream.range(0, 5) IntStream.range(0, 15)
.mapToObj(minerId -> new Miner(minerId, blockChain)) .mapToObj(minerId -> new Miner(minerId, blockChain))
.forEach(minerExecutor::submit); .forEach(minerExecutor::submit);
@@ -42,10 +42,10 @@ public final class Main {
minerExecutor.shutdownNow(); minerExecutor.shutdownNow();
} }
chatExecutor.shutdown(); transactionsExecutor.shutdown();
if (!chatExecutor.awaitTermination(60, TimeUnit.SECONDS)) { if (!transactionsExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
chatExecutor.shutdownNow(); transactionsExecutor.shutdownNow();
} }
FileManagement.saveBlockChain(blockChain); 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 id;
private final int magicNumber; private final int magicNumber;
private final float generationSecs; private final float generationSecs;
private final List<Message> chatMessages; private final List<Transaction> transactions;
private final long timeStamp; private final long timeStamp;
public Block( public Block(
@@ -25,7 +25,7 @@ public class Block implements Serializable {
final int id, final int id,
final int magicNumber, final int magicNumber,
final float generationSecs, final float generationSecs,
final List<Message> blockMessages final List<Transaction> blockTransactions
) { ) {
this.previousBlockHash = previousBlockHash; this.previousBlockHash = previousBlockHash;
this.blockHash = blockHash; this.blockHash = blockHash;
@@ -33,7 +33,7 @@ public class Block implements Serializable {
this.id = id; this.id = id;
this.magicNumber = magicNumber; this.magicNumber = magicNumber;
this.generationSecs = generationSecs; this.generationSecs = generationSecs;
this.chatMessages = blockMessages; this.transactions = blockTransactions;
this.timeStamp = new Date().getTime(); this.timeStamp = new Date().getTime();
} }
@@ -48,6 +48,7 @@ public class Block implements Serializable {
public String toString() { public String toString() {
return "Block: " + "\n" return "Block: " + "\n"
+ "Created by miner #" + this.minerId + "\n" + "Created by miner #" + this.minerId + "\n"
+ "miner #" + this.minerId + " gets 100 VC\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"
@@ -59,13 +60,13 @@ public class Block implements Serializable {
} }
public String messagesToString() { public String messagesToString() {
return chatMessages.isEmpty() ? "Empty block\n" : chatMessages.stream() return transactions.isEmpty() ? "No transactions\n" : transactions.stream()
.map(m -> String.valueOf(m.getId()).concat(" - ").concat(m.getText()).concat("\n")) .map(m -> "ID: ".concat(String.valueOf(m.getId()).concat(" - ").concat(m.getText()).concat("\n")))
.collect(Collectors.joining()); .collect(Collectors.joining());
} }
public List<Message> getMessages() { public List<Transaction> getMessages() {
return chatMessages; return transactions;
} }
} }

View File

@@ -10,6 +10,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import util.FileManagement; import util.FileManagement;
import util.HashFunction; import util.HashFunction;
import util.Security; import util.Security;
import exceptions.NotEnoughCoinsException;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -24,8 +25,10 @@ public class BlockChain implements Serializable {
private final Random random = new Random(); private final Random random = new Random();
private final List<Block> blockList = new LinkedList<>(); private final List<Block> blockList = new LinkedList<>();
private final List<Message> incomingChatMessages = new ArrayList<>(); private final List<Transaction> incomingTransactions = new ArrayList<>();
private final AtomicInteger lastMessageId = new AtomicInteger(1); 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 hashZeroes;
private int magicNumber; 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 previousBlockHash = (nextId > 0) ? blockList.get(nextId - 1).getBlockHash() : "0";
final String blockHash = calculateBlockHash(minerId); final String blockHash = calculateBlockHash(minerId);
incomingChatMessages.sort(Comparator.comparingInt(Message::getId)); incomingTransactions.sort(Comparator.comparingInt(Transaction::getId));
final var block = new Block( final var block = new Block(
previousBlockHash, previousBlockHash,
@@ -59,11 +62,12 @@ public class BlockChain implements Serializable {
nextId, nextId,
magicNumber, magicNumber,
generationSecs, generationSecs,
incomingChatMessages incomingTransactions
); );
blockList.add(block); blockList.add(block);
minersCoins.put(minerId, 100);
System.out.println(block); System.out.println(block);
incomingChatMessages.clear(); incomingTransactions.clear();
if (generationSecs < LOWER_LIMIT_SECS) { if (generationSecs < LOWER_LIMIT_SECS) {
hashZeroes += 1; hashZeroes += 1;
@@ -77,19 +81,52 @@ public class BlockChain implements Serializable {
} }
public int getNextMessageId() { public void acceptTransaction(
return lastMessageId.getAndIncrement(); 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) public void acceptUser(final String username, final int coins){
throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, IOException, InvalidKeySpecException { usersCoins.put(username, coins);
incomingChatMessages.add(new Message( }
nextMessageId,
name, public void transferCoins(final String sender, final int amount, final String receiver) {
text, final int prevSenderCoins = usersCoins.get(sender);
Security.getPrivate(), final int prevReceiverCoins = usersCoins.get(receiver);
Security.getPublic()
)); 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() { public boolean validateBlockchain() {
@@ -109,13 +146,13 @@ public class BlockChain implements Serializable {
if (!blockList.stream() if (!blockList.stream()
.map(Block::getMessages) .map(Block::getMessages)
.flatMap(Collection::stream) .flatMap(Collection::stream)
.map(Message::getId) .map(Transaction::getId)
.sorted() .sorted()
.collect(Collectors.toList()) .collect(Collectors.toList())
.equals(blockList.stream() .equals(blockList.stream()
.map(Block::getMessages) .map(Block::getMessages)
.flatMap(Collection::stream) .flatMap(Collection::stream)
.map(Message::getId) .map(Transaction::getId)
.collect(Collectors.toList()) .collect(Collectors.toList())
) )
) { ) {

View File

@@ -5,7 +5,7 @@ import util.Security;
import java.io.Serializable; import java.io.Serializable;
import java.security.*; import java.security.*;
public class Message implements Serializable { public class Transaction implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@@ -14,10 +14,16 @@ public class Message implements Serializable {
private final byte[] signature; private final byte[] signature;
private final PublicKey publicKey; private final PublicKey publicKey;
public Message(final int id, final String name, final String text, final PrivateKey privateKey, final PublicKey publicKey) public Transaction(
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { 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.id = id;
this.text = "Chatter " + name + " says: " + text; this.text = sender + " sent " + amount + " VC to " + receiver;
this.signature = Security.sign(text, privateKey); this.signature = Security.sign(text, privateKey);
this.publicKey = publicKey; this.publicKey = publicKey;
} }

View File

@@ -7,7 +7,7 @@ import java.security.*;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec; import java.security.spec.X509EncodedKeySpec;
import model.Message; import model.Transaction;
public final class Security { public final class Security {
@@ -42,7 +42,7 @@ public final class Security {
return rsa.sign(); 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()); 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();
}
}