diff --git a/.gitignore b/.gitignore index 1e5d2d9..2cd97a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,61 +1,10 @@ -/.idea/.gitignore -/build/reports/tests/test/css/base-style.css -/build/classes/java/main/model/Block.class -/out/production/blockchain_gitea/model/Block.class -/build/classes/java/main/model/BlockChain.class -/out/production/blockchain_gitea/model/BlockChain.class -/blockChain.txt -/build/classes/java/test/BlockChainTest.class -/build/reports/tests/test/classes/BlockChainTest.html -/.gradle/buildOutputCleanup/buildOutputCleanup.lock -/.gradle/buildOutputCleanup/cache.properties -/.gradle/checksums/checksums.lock -/.idea/compiler.xml -/build/reports/tests/test/packages/default-package.html -/.gradle/7.1/dependencies-accessors/dependencies-accessors.lock -/.gradle/7.1/executionHistory/executionHistory.lock -/.gradle/7.1/fileHashes/fileHashes.lock -/build/classes/java/main/util/FileManagement.class -/out/production/blockchain_gitea/util/FileManagement.class -/.gradle/7.1/dependencies-accessors/gc.properties -/.gradle/7.1/gc.properties -/.gradle/vcs-1/gc.properties -/.idea/gradle.xml -/gradle/wrapper/gradle-wrapper.jar -/gradle/wrapper/gradle-wrapper.properties +/.idea/* +/.gradle/* +/build/* +/out/* +/gradle/* /gradlew /gradlew.bat -/build/classes/java/main/util/HashFunction.class -/out/production/blockchain_gitea/util/HashFunction.class -/build/reports/tests/test/index.html -/.idea/sonarlint/issuestore/index.pb -/.idea/jarRepositories.xml -/.gradle/7.1/fileChanges/last-build.bin -/build/classes/java/main/Main.class -/out/production/blockchain_gitea/Main.class -/build/classes/java/main/model/Miner.class -/out/production/blockchain_gitea/model/Miner.class -/.idea/misc.xml -/build/classes/java/main/exceptions/NoSelfTransactionException.class -/build/classes/java/main/exceptions/NotEnoughCoinsException.class -/out/production/blockchain_gitea/exceptions/NotEnoughCoinsException.class -/build/test-results/test/binary/output.bin -/build/test-results/test/binary/output.bin.idx -/build/tmp/compileJava/previous-compilation-data.bin -/build/tmp/compileTestJava/previous-compilation-data.bin +/blockchain.txt /KeyPair/privateKey -/.idea/inspectionProfiles/Project_Default.xml -/KeyPair/publicKey -/build/reports/tests/test/js/report.js -/build/test-results/test/binary/results.bin -/.idea/runConfigurations.xml -/build/classes/java/main/util/Security.class -/out/production/blockchain_gitea/util/Security.class -/build/reports/tests/test/css/style.css -/build/test-results/test/TEST-BlockChainTest.xml -/build/classes/java/main/model/Transaction.class -/out/production/blockchain_gitea/model/Transaction.class -/build/classes/java/main/util/TransactionsGenerator.class -/out/production/blockchain_gitea/util/TransactionsGenerator.class -/.idea/uiDesigner.xml -/.idea/vcs.xml +/KeyPair/publicKey \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..376fcd8 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +

Blockchain

+ +Blockchain implementation with following characteristics: + +- Ability to store transactions between users. +- Mining reward for each block added to the blockchain. +- Proof of work with magic number self-balancing hashing. +- SHA-256 algorithm based hashing. + +Main method allows to test it by creating fake miners, and it is also included a transaction generator to mock a series of users interacting each other. +Execution example: + + ![](src/main/resources/img/blockchain.png) \ No newline at end of file diff --git a/src/main/java/Main.java b/src/main/java/Main.java index f4111ed..f1af7e8 100644 --- a/src/main/java/Main.java +++ b/src/main/java/Main.java @@ -3,7 +3,7 @@ import java.io.IOException; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; -import model.BlockChain; +import model.Blockchain; import util.TransactionsGenerator; import model.Miner; import util.FileManagement; @@ -14,7 +14,7 @@ public final class Main { public static void main(final String[] args) throws InterruptedException, IOException { final var nThreads = Runtime.getRuntime().availableProcessors(); - final var blockChain = BlockChain.getInstance(); + final var blockchain = Blockchain.getInstance(); // Cryptographic keys management final File publicKey = new File(Security.PUBLIC_KEY); @@ -27,13 +27,13 @@ public final class Main { final var transactionsExecutor = Executors.newScheduledThreadPool(nThreads); // Mocks a generator to send transactions into the blockchain - transactionsExecutor.scheduleAtFixedRate(new TransactionsGenerator(blockChain), 0, 200, TimeUnit.MILLISECONDS); + transactionsExecutor.scheduleAtFixedRate(new TransactionsGenerator(blockchain), 0, 200, TimeUnit.MILLISECONDS); final var minerExecutor = Executors.newFixedThreadPool(nThreads); // Creation of 15 miners IntStream.range(0, 15) - .mapToObj(minerId -> new Miner(minerId, blockChain)) + .mapToObj(minerId -> new Miner(minerId, blockchain)) .forEach(minerExecutor::submit); minerExecutor.shutdown(); @@ -48,7 +48,7 @@ public final class Main { transactionsExecutor.shutdownNow(); } - FileManagement.saveBlockChain(blockChain); + FileManagement.saveBlockchain(blockchain); } diff --git a/src/main/java/model/Block.java b/src/main/java/model/Block.java index f6952a4..7ab5d9a 100644 --- a/src/main/java/model/Block.java +++ b/src/main/java/model/Block.java @@ -48,7 +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" + + "miner #" + this.minerId + " gets 100 KarmaCoins\n" + "Id: " + this.id + "\n" + "Timestamp: " + this.timeStamp + "\n" + "Magic number: " + this.magicNumber + "\n" @@ -61,7 +61,7 @@ public class Block implements Serializable { public String messagesToString() { return transactions.isEmpty() ? "No transactions\n" : transactions.stream() - .map(m -> "ID: ".concat(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()); } diff --git a/src/main/java/model/BlockChain.java b/src/main/java/model/Blockchain.java similarity index 94% rename from src/main/java/model/BlockChain.java rename to src/main/java/model/Blockchain.java index 39dd89a..16c5fac 100644 --- a/src/main/java/model/BlockChain.java +++ b/src/main/java/model/Blockchain.java @@ -18,7 +18,7 @@ import java.util.stream.Collectors; import static java.lang.String.valueOf; -public class BlockChain implements Serializable { +public class Blockchain implements Serializable { private static final long serialVersionUID = 1L; private static final float LOWER_LIMIT_SECS = 0.1F; @@ -35,17 +35,17 @@ public class BlockChain implements Serializable { private int magicNumber; private float generationSecs = 0; - public static BlockChain getInstance() { + public static Blockchain getInstance() { try { - final BlockChain blockChain = (BlockChain) FileManagement.loadBlockChain(); - if (!blockChain.validateBlockchain()) { + final Blockchain blockchain = (Blockchain) FileManagement.loadBlockchain(); + if (!blockchain.validateBlockchain()) { System.out.println("Blockchain not valid! Creating new one"); - return new BlockChain(); + return new Blockchain(); } else { - return blockChain; + return blockchain; } } catch (final ClassNotFoundException | IOException e) { - return new BlockChain(); + return new Blockchain(); } } diff --git a/src/main/java/model/Miner.java b/src/main/java/model/Miner.java index 6586b96..441326e 100644 --- a/src/main/java/model/Miner.java +++ b/src/main/java/model/Miner.java @@ -3,16 +3,16 @@ package model; public class Miner implements Runnable { private final int minerId; - private final BlockChain blockChain; + private final Blockchain blockchain; - public Miner(final int minerId, final BlockChain blockChain) { + public Miner(final int minerId, final Blockchain blockchain) { this.minerId = minerId; - this.blockChain = blockChain; + this.blockchain = blockchain; } @Override public void run() { - blockChain.addBlock(minerId); + blockchain.addBlock(minerId); } } diff --git a/src/main/java/model/Transaction.java b/src/main/java/model/Transaction.java index 9419ae1..171da38 100644 --- a/src/main/java/model/Transaction.java +++ b/src/main/java/model/Transaction.java @@ -23,7 +23,7 @@ public class Transaction implements Serializable { final PublicKey publicKey ) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { this.id = id; - this.text = sender + " sent " + amount + " VC to " + receiver; + this.text = sender + " sent " + amount + " KarmaCoins to " + receiver; this.signature = Security.sign(text, privateKey); this.publicKey = publicKey; } diff --git a/src/main/java/util/FileManagement.java b/src/main/java/util/FileManagement.java index 0ec215a..bfabf90 100644 --- a/src/main/java/util/FileManagement.java +++ b/src/main/java/util/FileManagement.java @@ -11,13 +11,13 @@ import java.io.ObjectOutputStream; public final class FileManagement { - private static final String BLOCKCHAIN = "blockChain.txt"; + private static final String BLOCKCHAIN = "blockchain.txt"; private FileManagement() { throw new IllegalStateException("FileManagement class"); } - public static void saveBlockChain(final Object obj) throws IOException { + public static void saveBlockchain(final Object obj) throws IOException { final FileOutputStream fos = new FileOutputStream(BLOCKCHAIN); final BufferedOutputStream bos = new BufferedOutputStream(fos); final ObjectOutputStream oos = new ObjectOutputStream(bos); @@ -25,7 +25,7 @@ public final class FileManagement { oos.close(); } - public static Object loadBlockChain() throws IOException, ClassNotFoundException { + public static Object loadBlockchain() throws IOException, ClassNotFoundException { final FileInputStream fis = new FileInputStream(BLOCKCHAIN); final BufferedInputStream bis = new BufferedInputStream(fis); final ObjectInputStream ois = new ObjectInputStream(bis); diff --git a/src/main/java/util/TransactionsGenerator.java b/src/main/java/util/TransactionsGenerator.java index c19394d..f9dae46 100644 --- a/src/main/java/util/TransactionsGenerator.java +++ b/src/main/java/util/TransactionsGenerator.java @@ -2,14 +2,7 @@ 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 model.Blockchain; import java.util.Random; public class TransactionsGenerator implements Runnable { @@ -20,11 +13,11 @@ public class TransactionsGenerator implements Runnable { "Thom", "Jonny", "Ed", "Colin", "Phil" ).toArray(String[]::new); - private final BlockChain blockChain; + private final Blockchain blockchain; - public TransactionsGenerator(final BlockChain blockChain) { - this.blockChain = blockChain; - if (blockChain.getUsersCoins().isEmpty()) { + public TransactionsGenerator(final Blockchain blockchain) { + this.blockchain = blockchain; + if (blockchain.getUsersCoins().isEmpty()) { setInitialUserCoins(); } } @@ -32,20 +25,18 @@ public class TransactionsGenerator implements Runnable { @Override public void run() { try { - blockChain.acceptTransaction( - blockChain.getNextTransactionId(), + blockchain.acceptTransaction( + blockchain.getNextTransactionId(), getRandomUserName(), getRandomAmount(), getRandomUserName() ); - } catch (final NoSuchAlgorithmException | - SignatureException | - InvalidKeyException | - IOException | - InvalidKeySpecException | - NotEnoughCoinsException e) - { - e.printStackTrace(); +/* + Catching throwable instead of exception to avoid ScheduledExecutorService from stop working + because any thrown exception or error reaching the executor causes the executor to halt. +*/ + } catch (final Throwable t) { + t.printStackTrace(); } } @@ -55,13 +46,13 @@ public class TransactionsGenerator implements Runnable { private int getRandomAmount(){ final int MIN_AMOUNT = 1; - final int MAX_AMOUNT = 100; + final int MAX_AMOUNT = 50; return random.nextInt((MAX_AMOUNT - MIN_AMOUNT) + 1) + MIN_AMOUNT; } private void setInitialUserCoins() { - Arrays.stream(userNames).forEach(userName -> blockChain.acceptUser(userName, INITIAL_COINS)); + Arrays.stream(userNames).forEach(userName -> blockchain.acceptUser(userName, INITIAL_COINS)); } } \ No newline at end of file diff --git a/src/main/resources/img/blockchain.png b/src/main/resources/img/blockchain.png new file mode 100644 index 0000000..a9aeeec Binary files /dev/null and b/src/main/resources/img/blockchain.png differ diff --git a/src/test/java/BlockChainTest.java b/src/test/java/BlockchainTest.java similarity index 80% rename from src/test/java/BlockChainTest.java rename to src/test/java/BlockchainTest.java index 6d84f1a..8240106 100644 --- a/src/test/java/BlockChainTest.java +++ b/src/test/java/BlockchainTest.java @@ -13,24 +13,24 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.IntStream; -import model.BlockChain; +import model.Blockchain; import model.Miner; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class BlockChainTest { +class BlockchainTest { private final int nThreads = Runtime.getRuntime().availableProcessors(); private List miners; - private BlockChain blockChain; + private Blockchain blockchain; @BeforeEach public void setUp() { - blockChain = new BlockChain(); - blockChain.acceptUser("tester1", 100); - blockChain.acceptUser("tester2", 100); + blockchain = new Blockchain(); + blockchain.acceptUser("tester1", 100); + blockchain.acceptUser("tester2", 100); miners = IntStream.range(0, 5) - .mapToObj(minerId -> new Miner(minerId, blockChain)) + .mapToObj(minerId -> new Miner(minerId, blockchain)) .collect(Collectors.toList()); } @@ -41,8 +41,8 @@ class BlockChainTest { final var minerExecutor = Executors.newFixedThreadPool(nThreads); for (final Miner miner: miners){ - blockChain.acceptTransaction(blockChain.getNextTransactionId(), "tester1", 10, "tester2"); - blockChain.acceptTransaction(blockChain.getNextTransactionId(), "tester2", 10, "tester1"); + blockchain.acceptTransaction(blockchain.getNextTransactionId(), "tester1", 10, "tester2"); + blockchain.acceptTransaction(blockchain.getNextTransactionId(), "tester2", 10, "tester1"); minerExecutor.execute(miner); } @@ -52,14 +52,14 @@ class BlockChainTest { minerExecutor.shutdownNow(); } - assertTrue(blockChain.validateBlockchain()); + assertTrue(blockchain.validateBlockchain()); } @Test void block_chain_not_enough_coins_test() { final NotEnoughCoinsException thrown = assertThrows( NotEnoughCoinsException.class, - () -> blockChain.acceptTransaction( + () -> blockchain.acceptTransaction( 1, "tester1", 110, @@ -72,7 +72,7 @@ class BlockChainTest { void block_chain_not_self_transaction_test() { final NoSelfTransactionException thrown = assertThrows( NoSelfTransactionException.class, - () -> blockChain.acceptTransaction( + () -> blockchain.acceptTransaction( 1, "tester1", 10,