Implemented transactions record
This commit is contained in:
18
build.gradle
Normal file
18
build.gradle
Normal 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'
|
||||
}
|
||||
@@ -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);
|
||||
9
src/main/java/exceptions/NotEnoughCoinsException.java
Normal file
9
src/main/java/exceptions/NotEnoughCoinsException.java
Normal 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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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())
|
||||
)
|
||||
) {
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
67
src/main/java/util/TransactionsGenerator.java
Normal file
67
src/main/java/util/TransactionsGenerator.java
Normal 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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user