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