From fe8e8211a7fd5b6849c1de0a7219328eb55a44c5 Mon Sep 17 00:00:00 2001 From: oleksii Date: Mon, 19 Nov 2018 00:33:14 +0200 Subject: [PATCH 01/14] First phase implementation done --- src/blockchain/Block.java | 40 +++++++++++++++++++++++++++++++ src/blockchain/Blockchain.java | 44 ++++++++++++++++++++++++++++++++++ src/blockchain/Main.java | 18 ++++++++++++-- src/blockchain/StringUtil.java | 28 ++++++++++++++++++++++ 4 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 src/blockchain/Block.java create mode 100644 src/blockchain/Blockchain.java create mode 100644 src/blockchain/StringUtil.java diff --git a/src/blockchain/Block.java b/src/blockchain/Block.java new file mode 100644 index 0000000..534b357 --- /dev/null +++ b/src/blockchain/Block.java @@ -0,0 +1,40 @@ +package blockchain; + +public class Block { + + private final Integer id; + private final String hashPreviousBlock; + private final String hash; + private final String data; + private final Long timestamp; + + + public Block(Integer id, String hashPreviousBlock, String hash, String data, + Long timestamp) { + this.id = id; + this.hashPreviousBlock = hashPreviousBlock; + this.hash = hash; + this.data = data; + this.timestamp = timestamp; + } + + public Integer getId() { + return id; + } + + public String getHashPreviousBlock() { + return hashPreviousBlock; + } + + public String getHash() { + return hash; + } + + public Long getTimestamp() { + return timestamp; + } + + public String getData() { + return data; + } +} diff --git a/src/blockchain/Blockchain.java b/src/blockchain/Blockchain.java new file mode 100644 index 0000000..39c97f6 --- /dev/null +++ b/src/blockchain/Blockchain.java @@ -0,0 +1,44 @@ +package blockchain; + +import java.util.Collections; +import java.util.Date; +import java.util.Iterator; +import java.util.LinkedList; + +public class Blockchain implements Iterable { + + private final LinkedList blocks; + + public Blockchain() { + blocks = new LinkedList<>(Collections.singleton( + new Block(1, + "0", + StringUtil.applySha256("0" + "pivot"), + "pivot", + new Date().getTime())) + ); + } + + public void generate() { + Long timestamp = new Date().getTime(); + Block tailBlock = blocks.getLast(); + String hash = StringUtil.applySha256(tailBlock.getHash() + "hello"); + blocks.add(new Block(tailBlock.getId() + 1, tailBlock.getHash(), hash, "hello", timestamp)); + } + + public boolean validate() { + for (int i = 1; i < blocks.size(); i++) { + Block prev = blocks.get(i - 1); + Block cur = blocks.get(i); + if (!cur.getHashPreviousBlock().equals(prev.getHash())) { + return false; + } + } + return true; + } + + @Override + public Iterator iterator() { + return blocks.iterator(); + } +} diff --git a/src/blockchain/Main.java b/src/blockchain/Main.java index adf1d02..c44806f 100644 --- a/src/blockchain/Main.java +++ b/src/blockchain/Main.java @@ -1,7 +1,21 @@ package blockchain; public class Main { - public static void main(String[] args) { - System.out.print("Hello world!"); + + public static void main(String[] args) { + Blockchain blockchain = new Blockchain(); + for (int i = 0; i < 10; i++) { + blockchain.generate(); + } + + for (Block block : blockchain) { + System.out.printf("Block:\n"); + System.out.printf("Id: %s\n", block.getId()); + System.out.printf("Timestamp: %s\n", block.getTimestamp()); + System.out.printf("Hash of the previous block:\n%s\n", block.getHashPreviousBlock()); + System.out.printf("Hash of the block: \n%s\n\n", block.getHash()); } + + System.out.println(blockchain.validate()); + } } \ No newline at end of file diff --git a/src/blockchain/StringUtil.java b/src/blockchain/StringUtil.java new file mode 100644 index 0000000..9a4c32d --- /dev/null +++ b/src/blockchain/StringUtil.java @@ -0,0 +1,28 @@ +package blockchain; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; + +public class StringUtil { + + /** + * Applies Sha256 to a string and returns a hash. + */ + public static String applySha256(String input) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hash = digest.digest(input.getBytes(StandardCharsets.UTF_8)); + StringBuilder hexString = new StringBuilder(); + for (byte elem : hash) { + String hex = Integer.toHexString(0xff & elem); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + return hexString.toString(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} From ddac0ff8d02695a247504226c30b9a4293f9054e Mon Sep 17 00:00:00 2001 From: oleksii Date: Tue, 27 Nov 2018 01:04:00 +0200 Subject: [PATCH 02/14] Second phase implementation done --- src/blockchain/Block.java | 12 +++++- src/blockchain/Blockchain.java | 69 +++++++++++++++++++++++++++------- src/blockchain/Main.java | 30 +++++++++------ src/blockchain/Persister.java | 43 +++++++++++++++++++++ 4 files changed, 127 insertions(+), 27 deletions(-) create mode 100644 src/blockchain/Persister.java diff --git a/src/blockchain/Block.java b/src/blockchain/Block.java index 534b357..6b92063 100644 --- a/src/blockchain/Block.java +++ b/src/blockchain/Block.java @@ -1,21 +1,25 @@ package blockchain; -public class Block { +import java.io.Serializable; + +public class Block implements Serializable { private final Integer id; private final String hashPreviousBlock; private final String hash; private final String data; private final Long timestamp; + private final Integer magicNumber; public Block(Integer id, String hashPreviousBlock, String hash, String data, - Long timestamp) { + Long timestamp, Integer magicNumber) { this.id = id; this.hashPreviousBlock = hashPreviousBlock; this.hash = hash; this.data = data; this.timestamp = timestamp; + this.magicNumber = magicNumber; } public Integer getId() { @@ -37,4 +41,8 @@ public Long getTimestamp() { public String getData() { return data; } + + public Integer getMagicNumber() { + return magicNumber; + } } diff --git a/src/blockchain/Blockchain.java b/src/blockchain/Blockchain.java index 39c97f6..c38e05d 100644 --- a/src/blockchain/Blockchain.java +++ b/src/blockchain/Blockchain.java @@ -4,29 +4,63 @@ import java.util.Date; import java.util.Iterator; import java.util.LinkedList; +import java.util.List; +import java.util.Random; +import java.util.stream.Stream; public class Blockchain implements Iterable { private final LinkedList blocks; + private final Random random; + private final String prefix; + private final Persister persister; - public Blockchain() { - blocks = new LinkedList<>(Collections.singleton( - new Block(1, - "0", - StringUtil.applySha256("0" + "pivot"), - "pivot", - new Date().getTime())) - ); + public Blockchain(int N) { + persister = new Persister("blockchain.ser"); + random = new Random(); + prefix = Stream.iterate("0", x -> "0").limit(N).reduce("", (x, y) -> x + y); + if (persister.existsChain()) { + LinkedList blocks = (LinkedList) persister.load(); + if (validate(blocks)) { + this.blocks = blocks; + } else { + Block firstBlock = initialBlockHead(); + this.blocks = new LinkedList<>(Collections.singleton(firstBlock)); + } + } else { + Block firstBlock = initialBlockHead(); + blocks = new LinkedList<>(Collections.singleton(firstBlock)); + } + persister.save(blocks); } + public void generate() { Long timestamp = new Date().getTime(); Block tailBlock = blocks.getLast(); - String hash = StringUtil.applySha256(tailBlock.getHash() + "hello"); - blocks.add(new Block(tailBlock.getId() + 1, tailBlock.getHash(), hash, "hello", timestamp)); + Integer magicNumber = random.nextInt(); + String hash = StringUtil.applySha256(tailBlock.getHash() + "hello" + magicNumber); + while (!hash.startsWith(prefix)) { + magicNumber = random.nextInt(); + hash = StringUtil.applySha256(tailBlock.getHash() + "hello" + magicNumber); + } + + Block newBlock = new Block(tailBlock.getId() + 1, tailBlock.getHash(), hash, "hello", timestamp, + magicNumber); + blocks.add(newBlock); + persister.save(blocks); } public boolean validate() { + return validate(blocks); + } + + @Override + public Iterator iterator() { + return blocks.iterator(); + } + + private boolean validate(List blocks) { for (int i = 1; i < blocks.size(); i++) { Block prev = blocks.get(i - 1); Block cur = blocks.get(i); @@ -37,8 +71,17 @@ public boolean validate() { return true; } - @Override - public Iterator iterator() { - return blocks.iterator(); + private Block initialBlockHead() { + Integer magicNumber = random.nextInt(); + String hash = StringUtil.applySha256("0" + magicNumber); + while (!hash.startsWith(prefix)) { + magicNumber = random.nextInt(); + hash = StringUtil.applySha256("0" + magicNumber); + } + return new Block(1, + "0", + hash, + "empty", + new Date().getTime(), magicNumber); } } diff --git a/src/blockchain/Main.java b/src/blockchain/Main.java index c44806f..d67a9ad 100644 --- a/src/blockchain/Main.java +++ b/src/blockchain/Main.java @@ -1,21 +1,27 @@ package blockchain; +import java.util.Scanner; + public class Main { public static void main(String[] args) { - Blockchain blockchain = new Blockchain(); - for (int i = 0; i < 10; i++) { - blockchain.generate(); - } + try (Scanner scanner = new Scanner(System.in)) { + System.out.printf("Enter how many zeros the hash must starts with: "); + Blockchain blockchain = new Blockchain(scanner.nextInt()); + for (int i = 0; i < 10; i++) { + blockchain.generate(); + } - for (Block block : blockchain) { - System.out.printf("Block:\n"); - System.out.printf("Id: %s\n", block.getId()); - System.out.printf("Timestamp: %s\n", block.getTimestamp()); - System.out.printf("Hash of the previous block:\n%s\n", block.getHashPreviousBlock()); - System.out.printf("Hash of the block: \n%s\n\n", block.getHash()); - } + for (Block block : blockchain) { + System.out.printf("Block:\n"); + System.out.printf("Id: %s\n", block.getId()); + System.out.printf("Timestamp: %s\n", block.getTimestamp()); + System.out.printf("Magic number: %s\n", block.getMagicNumber()); + System.out.printf("Hash of the previous block:\n%s\n", block.getHashPreviousBlock()); + System.out.printf("Hash of the block: \n%s\n\n", block.getHash()); + } - System.out.println(blockchain.validate()); + System.out.println(blockchain.validate()); + } } } \ No newline at end of file diff --git a/src/blockchain/Persister.java b/src/blockchain/Persister.java new file mode 100644 index 0000000..7b67c12 --- /dev/null +++ b/src/blockchain/Persister.java @@ -0,0 +1,43 @@ +package blockchain; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; + +public class Persister { + + + private String filename; + + public Persister(String filename) { + this.filename = filename; + } + + public void save(List blockchain) { + try (FileOutputStream fileOutputStream = new FileOutputStream(filename); + ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)) { + objectOutputStream.writeObject(blockchain); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public List load() { + try (FileInputStream fileInputStream = new FileInputStream(filename); + ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)) { + return (List) objectInputStream.readObject(); + } catch (IOException | ClassNotFoundException e) { + e.printStackTrace(); + } + return null; + } + + public boolean existsChain() { + return Files.exists(Paths.get(filename)); + } +} From 7078dffbfbd4f7657a613f61779d9533524e038f Mon Sep 17 00:00:00 2001 From: oleksii Date: Mon, 3 Dec 2018 00:46:23 +0200 Subject: [PATCH 03/14] Second phase implementation done --- src/blockchain/Block.java | 8 +++- src/blockchain/Blockchain.java | 64 ++++++++++++------------------- src/blockchain/FilePersister.java | 47 +++++++++++++++++++++++ src/blockchain/Main.java | 13 +++++-- src/blockchain/Persister.java | 40 ++----------------- 5 files changed, 92 insertions(+), 80 deletions(-) create mode 100644 src/blockchain/FilePersister.java diff --git a/src/blockchain/Block.java b/src/blockchain/Block.java index 6b92063..ec7c110 100644 --- a/src/blockchain/Block.java +++ b/src/blockchain/Block.java @@ -10,16 +10,18 @@ public class Block implements Serializable { private final String data; private final Long timestamp; private final Integer magicNumber; + private final Long generationTime; public Block(Integer id, String hashPreviousBlock, String hash, String data, - Long timestamp, Integer magicNumber) { + Long timestamp, Integer magicNumber, Long generationTime) { this.id = id; this.hashPreviousBlock = hashPreviousBlock; this.hash = hash; this.data = data; this.timestamp = timestamp; this.magicNumber = magicNumber; + this.generationTime = generationTime; } public Integer getId() { @@ -45,4 +47,8 @@ public String getData() { public Integer getMagicNumber() { return magicNumber; } + + public Long getGenerationTime() { + return generationTime; + } } diff --git a/src/blockchain/Blockchain.java b/src/blockchain/Blockchain.java index c38e05d..83ff3f0 100644 --- a/src/blockchain/Blockchain.java +++ b/src/blockchain/Blockchain.java @@ -1,7 +1,6 @@ package blockchain; import java.util.Collections; -import java.util.Date; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -10,43 +9,32 @@ public class Blockchain implements Iterable { - private final LinkedList blocks; + private final List blocks; private final Random random; private final String prefix; private final Persister persister; - public Blockchain(int N) { - persister = new Persister("blockchain.ser"); - random = new Random(); - prefix = Stream.iterate("0", x -> "0").limit(N).reduce("", (x, y) -> x + y); - if (persister.existsChain()) { - LinkedList blocks = (LinkedList) persister.load(); - if (validate(blocks)) { - this.blocks = blocks; - } else { - Block firstBlock = initialBlockHead(); - this.blocks = new LinkedList<>(Collections.singleton(firstBlock)); - } + public Blockchain(int N, Persister persister) { + this.persister = persister; + this.random = new Random(); + this.prefix = Stream.iterate("0", x -> "0") + .limit(N) + .reduce("", (x, y) -> x + y); + + List blocks = persister.load(); + if (!blocks.isEmpty() && validate(blocks)) { + this.blocks = blocks; } else { - Block firstBlock = initialBlockHead(); - blocks = new LinkedList<>(Collections.singleton(firstBlock)); + this.blocks = new LinkedList<>(Collections.singleton( + generateNextBlock(1, "0", "empty"))); + persister.save(blocks); } - persister.save(blocks); } public void generate() { - Long timestamp = new Date().getTime(); - Block tailBlock = blocks.getLast(); - Integer magicNumber = random.nextInt(); - String hash = StringUtil.applySha256(tailBlock.getHash() + "hello" + magicNumber); - while (!hash.startsWith(prefix)) { - magicNumber = random.nextInt(); - hash = StringUtil.applySha256(tailBlock.getHash() + "hello" + magicNumber); - } - - Block newBlock = new Block(tailBlock.getId() + 1, tailBlock.getHash(), hash, "hello", timestamp, - magicNumber); + Block tailBlock = blocks.get(blocks.size() - 1); + Block newBlock = generateNextBlock(tailBlock.getId() + 1, tailBlock.getHash(), "hello"); blocks.add(newBlock); persister.save(blocks); } @@ -71,17 +59,15 @@ private boolean validate(List blocks) { return true; } - private Block initialBlockHead() { - Integer magicNumber = random.nextInt(); - String hash = StringUtil.applySha256("0" + magicNumber); - while (!hash.startsWith(prefix)) { + private Block generateNextBlock(int id, String hashPreviousBlock, String data) { + long timestamp = System.currentTimeMillis(); + Integer magicNumber; + String hash; + do { magicNumber = random.nextInt(); - hash = StringUtil.applySha256("0" + magicNumber); - } - return new Block(1, - "0", - hash, - "empty", - new Date().getTime(), magicNumber); + hash = StringUtil.applySha256(hashPreviousBlock + magicNumber); + } while (!hash.startsWith(prefix)); + return new Block(id, hashPreviousBlock, hash, data, timestamp, magicNumber, + System.currentTimeMillis() - timestamp); } } diff --git a/src/blockchain/FilePersister.java b/src/blockchain/FilePersister.java new file mode 100644 index 0000000..2b08d9e --- /dev/null +++ b/src/blockchain/FilePersister.java @@ -0,0 +1,47 @@ +package blockchain; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; + +class FilePersister implements Persister { + + + private final String filename; + + public FilePersister(String filename) { + this.filename = filename; + } + + @Override + public void save(List blockchain) { + try (FileOutputStream fileOutputStream = new FileOutputStream(filename); + ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)) { + objectOutputStream.writeObject(blockchain); + } catch (IOException e) { + throw new RuntimeException(); + } + } + + @Override + public List load() { + if (fileExists()) { + try (FileInputStream fileInputStream = new FileInputStream(filename); + ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)) { + return (List) objectInputStream.readObject(); + } catch (IOException | ClassNotFoundException e) { + return List.of(); + } + } + return List.of(); + } + + private boolean fileExists() { + return Files.exists(Paths.get(filename)); + } +} diff --git a/src/blockchain/Main.java b/src/blockchain/Main.java index d67a9ad..492d299 100644 --- a/src/blockchain/Main.java +++ b/src/blockchain/Main.java @@ -6,8 +6,11 @@ public class Main { public static void main(String[] args) { try (Scanner scanner = new Scanner(System.in)) { - System.out.printf("Enter how many zeros the hash must starts with: "); - Blockchain blockchain = new Blockchain(scanner.nextInt()); + System.out.print("Enter how many zeros the hash must starts with: "); + + Persister persister = new FilePersister("blockchain.ser"); + Blockchain blockchain = new Blockchain(scanner.nextInt(), persister); + for (int i = 0; i < 10; i++) { blockchain.generate(); } @@ -18,10 +21,12 @@ public static void main(String[] args) { System.out.printf("Timestamp: %s\n", block.getTimestamp()); System.out.printf("Magic number: %s\n", block.getMagicNumber()); System.out.printf("Hash of the previous block:\n%s\n", block.getHashPreviousBlock()); - System.out.printf("Hash of the block: \n%s\n\n", block.getHash()); + System.out.printf("Hash of the block: \n%s\n", block.getHash()); + System.out + .printf("Block was generating for: %s seconds\n\n", block.getGenerationTime() / 1000); } - System.out.println(blockchain.validate()); + System.out.printf("Valid: %s\n", blockchain.validate()); } } } \ No newline at end of file diff --git a/src/blockchain/Persister.java b/src/blockchain/Persister.java index 7b67c12..fe58027 100644 --- a/src/blockchain/Persister.java +++ b/src/blockchain/Persister.java @@ -1,43 +1,11 @@ package blockchain; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.nio.file.Files; -import java.nio.file.Paths; import java.util.List; -public class Persister { +public interface Persister { + void save(List blockchain); - private String filename; - - public Persister(String filename) { - this.filename = filename; - } - - public void save(List blockchain) { - try (FileOutputStream fileOutputStream = new FileOutputStream(filename); - ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)) { - objectOutputStream.writeObject(blockchain); - } catch (IOException e) { - e.printStackTrace(); - } - } - - public List load() { - try (FileInputStream fileInputStream = new FileInputStream(filename); - ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)) { - return (List) objectInputStream.readObject(); - } catch (IOException | ClassNotFoundException e) { - e.printStackTrace(); - } - return null; - } - - public boolean existsChain() { - return Files.exists(Paths.get(filename)); - } + List load(); } + From ee39a82633d5afcb257bbaa94f1cd30e490a9220 Mon Sep 17 00:00:00 2001 From: oleksii Date: Sat, 8 Dec 2018 16:04:54 +0200 Subject: [PATCH 04/14] Third phase implementation done --- src/blockchain/Block.java | 9 ++- src/blockchain/BlockGenerator.java | 25 ++++++++ src/blockchain/Blockchain.java | 98 +++++++++++++++++++----------- src/blockchain/Main.java | 37 +++++------ src/blockchain/miners/Miner.java | 27 ++++++++ 5 files changed, 140 insertions(+), 56 deletions(-) create mode 100644 src/blockchain/BlockGenerator.java create mode 100644 src/blockchain/miners/Miner.java diff --git a/src/blockchain/Block.java b/src/blockchain/Block.java index ec7c110..806bdcf 100644 --- a/src/blockchain/Block.java +++ b/src/blockchain/Block.java @@ -5,6 +5,7 @@ public class Block implements Serializable { private final Integer id; + private final Long minerId; private final String hashPreviousBlock; private final String hash; private final String data; @@ -13,9 +14,11 @@ public class Block implements Serializable { private final Long generationTime; - public Block(Integer id, String hashPreviousBlock, String hash, String data, + public Block(Integer id, Long minerId, String hashPreviousBlock, String hash, + String data, Long timestamp, Integer magicNumber, Long generationTime) { this.id = id; + this.minerId = minerId; this.hashPreviousBlock = hashPreviousBlock; this.hash = hash; this.data = data; @@ -51,4 +54,8 @@ public Integer getMagicNumber() { public Long getGenerationTime() { return generationTime; } + + public Long getMinerId() { + return minerId; + } } diff --git a/src/blockchain/BlockGenerator.java b/src/blockchain/BlockGenerator.java new file mode 100644 index 0000000..498a247 --- /dev/null +++ b/src/blockchain/BlockGenerator.java @@ -0,0 +1,25 @@ +package blockchain; + +import java.util.Random; + +public class BlockGenerator { + + private final Random random; + + public BlockGenerator() { + this.random = new Random(); + } + + public Block generateNextBlock(int id, long minerId, String prefix, String previousHash, + String data) { + long timestamp = System.currentTimeMillis(); + Integer magicNumber; + String hash; + do { + magicNumber = random.nextInt(); + hash = StringUtil.applySha256(previousHash + magicNumber); + } while (!hash.startsWith(prefix)); + return new Block(id, minerId, previousHash, hash, data, timestamp, magicNumber, + System.currentTimeMillis() - timestamp); + } +} diff --git a/src/blockchain/Blockchain.java b/src/blockchain/Blockchain.java index 83ff3f0..aff249b 100644 --- a/src/blockchain/Blockchain.java +++ b/src/blockchain/Blockchain.java @@ -1,54 +1,95 @@ package blockchain; import java.util.Collections; -import java.util.Iterator; import java.util.LinkedList; import java.util.List; -import java.util.Random; +import java.util.concurrent.TimeUnit; import java.util.stream.Stream; -public class Blockchain implements Iterable { +public class Blockchain { + + private static final Long ACCEPTABLE_TIME = TimeUnit.SECONDS.toMillis(6); private final List blocks; - private final Random random; - private final String prefix; + private int prefixLength; private final Persister persister; - public Blockchain(int N, Persister persister) { + public Blockchain(Persister persister, BlockGenerator blockGenerator) { this.persister = persister; - this.random = new Random(); - this.prefix = Stream.iterate("0", x -> "0") - .limit(N) - .reduce("", (x, y) -> x + y); - + this.prefixLength = 0; List blocks = persister.load(); - if (!blocks.isEmpty() && validate(blocks)) { + if (!blocks.isEmpty() && validateAllChain(blocks)) { this.blocks = blocks; } else { this.blocks = new LinkedList<>(Collections.singleton( - generateNextBlock(1, "0", "empty"))); + blockGenerator.generateNextBlock(1, 0, "", "0", ""))); persister.save(blocks); } } + public synchronized void accept(Block newBlock) { + if (isValid(newBlock)) { + outputAndAdjust(newBlock); + blocks.add(newBlock); + persister.save(blocks); + } + } + + public synchronized String getPrefix() { + return Stream.iterate("0", x -> "0") + .limit(prefixLength) + .reduce("", (x, y) -> x + y); + } + + public synchronized Block tail() { + return blocks.get(blocks.size() - 1); + } - public void generate() { + private boolean isValid(Block newBlock) { Block tailBlock = blocks.get(blocks.size() - 1); - Block newBlock = generateNextBlock(tailBlock.getId() + 1, tailBlock.getHash(), "hello"); - blocks.add(newBlock); - persister.save(blocks); + if (!newBlock.getHash().startsWith(getPrefix()) || !newBlock.getHashPreviousBlock() + .equals(tailBlock.getHash())) { + return false; + } + return true; + } + + private void outputAndAdjust(Block newBlock) { + outputStats(newBlock); + adjustComplexity(newBlock); + } + + private void outputStats(Block newBlock) { + System.out.printf("Block:\n"); + System.out.printf("Id: %s\n", newBlock.getId()); + System.out.printf("Created by miner # %s\n", newBlock.getMinerId()); + System.out.printf("Timestamp: %s\n", newBlock.getTimestamp()); + System.out.printf("Magic number: %s\n", newBlock.getMagicNumber()); + System.out.printf("Hash of the previous block:\n%s\n", newBlock.getHashPreviousBlock()); + System.out.printf("Hash of the block: \n%s\n", newBlock.getHash()); + System.out + .printf("Block was generating for: %s seconds\n", newBlock.getGenerationTime() / 1000); } - public boolean validate() { - return validate(blocks); + private void adjustComplexity(Block newBlock) { + if (!withinAcceptable(newBlock.getGenerationTime())) { + if (ACCEPTABLE_TIME < newBlock.getGenerationTime()) { + System.out.printf("N was decreased to %s\n\n", prefixLength); + prefixLength--; + } else { + System.out.printf("N was increased to %s\n\n", prefixLength); + prefixLength++; + } + } else { + System.out.printf("N stays the same\n\n"); + } } - @Override - public Iterator iterator() { - return blocks.iterator(); + private boolean withinAcceptable(Long generationTime) { + return Math.abs(generationTime - ACCEPTABLE_TIME) <= 1000; } - private boolean validate(List blocks) { + private boolean validateAllChain(List blocks) { for (int i = 1; i < blocks.size(); i++) { Block prev = blocks.get(i - 1); Block cur = blocks.get(i); @@ -59,15 +100,4 @@ private boolean validate(List blocks) { return true; } - private Block generateNextBlock(int id, String hashPreviousBlock, String data) { - long timestamp = System.currentTimeMillis(); - Integer magicNumber; - String hash; - do { - magicNumber = random.nextInt(); - hash = StringUtil.applySha256(hashPreviousBlock + magicNumber); - } while (!hash.startsWith(prefix)); - return new Block(id, hashPreviousBlock, hash, data, timestamp, magicNumber, - System.currentTimeMillis() - timestamp); - } } diff --git a/src/blockchain/Main.java b/src/blockchain/Main.java index 492d299..023ec28 100644 --- a/src/blockchain/Main.java +++ b/src/blockchain/Main.java @@ -1,32 +1,27 @@ package blockchain; -import java.util.Scanner; +import blockchain.miners.Miner; public class Main { - public static void main(String[] args) { - try (Scanner scanner = new Scanner(System.in)) { - System.out.print("Enter how many zeros the hash must starts with: "); + public static void main(String[] args) throws InterruptedException { - Persister persister = new FilePersister("blockchain.ser"); - Blockchain blockchain = new Blockchain(scanner.nextInt(), persister); + int NUMBER_OF_MINERS = 10; - for (int i = 0; i < 10; i++) { - blockchain.generate(); - } + Persister persister = new FilePersister("blockchain.ser"); + BlockGenerator blockGenerator = new BlockGenerator(); + Blockchain blockchain = new Blockchain(persister, blockGenerator); - for (Block block : blockchain) { - System.out.printf("Block:\n"); - System.out.printf("Id: %s\n", block.getId()); - System.out.printf("Timestamp: %s\n", block.getTimestamp()); - System.out.printf("Magic number: %s\n", block.getMagicNumber()); - System.out.printf("Hash of the previous block:\n%s\n", block.getHashPreviousBlock()); - System.out.printf("Hash of the block: \n%s\n", block.getHash()); - System.out - .printf("Block was generating for: %s seconds\n\n", block.getGenerationTime() / 1000); - } - - System.out.printf("Valid: %s\n", blockchain.validate()); + Thread[] miners = new Thread[NUMBER_OF_MINERS]; + for (int i = 0; i < NUMBER_OF_MINERS; i++) { + miners[i] = new Thread(new Miner(blockchain, blockGenerator, Long.valueOf(i))); + } + for (Thread miner : miners) { + miner.start(); + } + Thread.sleep(10000); + for (Thread miner : miners) { + miner.interrupt(); } } } \ No newline at end of file diff --git a/src/blockchain/miners/Miner.java b/src/blockchain/miners/Miner.java new file mode 100644 index 0000000..2c1dca2 --- /dev/null +++ b/src/blockchain/miners/Miner.java @@ -0,0 +1,27 @@ +package blockchain.miners; + +import blockchain.Block; +import blockchain.BlockGenerator; +import blockchain.Blockchain; + +public class Miner implements Runnable { + + private final Blockchain blockchain; + private BlockGenerator blockGenerator; + private final Long id; + + public Miner(Blockchain blockchain, BlockGenerator blockGenerator, Long id) { + this.blockchain = blockchain; + this.blockGenerator = blockGenerator; + this.id = id; + } + + @Override + public void run() { + while (!Thread.currentThread().isInterrupted()) { + Block prev = blockchain.tail(); + blockchain.accept(blockGenerator.generateNextBlock(prev.getId() + 1, id, + blockchain.getPrefix(), prev.getHash(), id.toString())); + } + } +} From 849cbc3322d934d5c96457fdd1b3c638f45dd351 Mon Sep 17 00:00:00 2001 From: oleksii Date: Sun, 9 Dec 2018 15:58:23 +0200 Subject: [PATCH 05/14] Fourth phase implementation done --- src/blockchain/Block.java | 3 +- src/blockchain/BlockGenerator.java | 25 ---------- src/blockchain/Blockchain.java | 48 +++++++++++++++---- src/blockchain/Main.java | 31 +++++++++--- src/blockchain/data/Message.java | 20 ++++++++ src/blockchain/data/format/DataFormatter.java | 6 +++ .../data/format/PlanMessageFormat.java | 11 +++++ src/blockchain/{ => io}/FilePersister.java | 5 +- src/blockchain/{ => io}/Persister.java | 3 +- src/blockchain/miners/Miner.java | 31 ++++++++---- src/blockchain/{ => utils}/StringUtil.java | 2 +- 11 files changed, 130 insertions(+), 55 deletions(-) delete mode 100644 src/blockchain/BlockGenerator.java create mode 100644 src/blockchain/data/Message.java create mode 100644 src/blockchain/data/format/DataFormatter.java create mode 100644 src/blockchain/data/format/PlanMessageFormat.java rename src/blockchain/{ => io}/FilePersister.java (92%) rename src/blockchain/{ => io}/Persister.java (71%) rename src/blockchain/{ => utils}/StringUtil.java (96%) diff --git a/src/blockchain/Block.java b/src/blockchain/Block.java index 806bdcf..7aee5e9 100644 --- a/src/blockchain/Block.java +++ b/src/blockchain/Block.java @@ -15,8 +15,7 @@ public class Block implements Serializable { public Block(Integer id, Long minerId, String hashPreviousBlock, String hash, - String data, - Long timestamp, Integer magicNumber, Long generationTime) { + String data, Long timestamp, Integer magicNumber, Long generationTime) { this.id = id; this.minerId = minerId; this.hashPreviousBlock = hashPreviousBlock; diff --git a/src/blockchain/BlockGenerator.java b/src/blockchain/BlockGenerator.java deleted file mode 100644 index 498a247..0000000 --- a/src/blockchain/BlockGenerator.java +++ /dev/null @@ -1,25 +0,0 @@ -package blockchain; - -import java.util.Random; - -public class BlockGenerator { - - private final Random random; - - public BlockGenerator() { - this.random = new Random(); - } - - public Block generateNextBlock(int id, long minerId, String prefix, String previousHash, - String data) { - long timestamp = System.currentTimeMillis(); - Integer magicNumber; - String hash; - do { - magicNumber = random.nextInt(); - hash = StringUtil.applySha256(previousHash + magicNumber); - } while (!hash.startsWith(prefix)); - return new Block(id, minerId, previousHash, hash, data, timestamp, magicNumber, - System.currentTimeMillis() - timestamp); - } -} diff --git a/src/blockchain/Blockchain.java b/src/blockchain/Blockchain.java index aff249b..ca9c04f 100644 --- a/src/blockchain/Blockchain.java +++ b/src/blockchain/Blockchain.java @@ -1,46 +1,73 @@ package blockchain; +import blockchain.data.format.DataFormatter; +import blockchain.io.Persister; +import blockchain.utils.StringUtil; import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.StringJoiner; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; -public class Blockchain { +public class Blockchain { - private static final Long ACCEPTABLE_TIME = TimeUnit.SECONDS.toMillis(6); + private static final Long ACCEPTABLE_TIME = TimeUnit.SECONDS.toMillis(3); private final List blocks; + private final List pendingMessages; private int prefixLength; private final Persister persister; + private DataFormatter dataFormatter; - public Blockchain(Persister persister, BlockGenerator blockGenerator) { + public Blockchain(Persister persister, + DataFormatter dataFormatter) { this.persister = persister; + this.dataFormatter = dataFormatter; this.prefixLength = 0; List blocks = persister.load(); if (!blocks.isEmpty() && validateAllChain(blocks)) { this.blocks = blocks; } else { this.blocks = new LinkedList<>(Collections.singleton( - blockGenerator.generateNextBlock(1, 0, "", "0", ""))); + new Block(0, 0L, "0", StringUtil.applySha256("0" + 0), + "no messages", + System.currentTimeMillis(), 0, 0L))); persister.save(blocks); } + pendingMessages = new LinkedList<>(); } public synchronized void accept(Block newBlock) { if (isValid(newBlock)) { outputAndAdjust(newBlock); blocks.add(newBlock); + pendingMessages.clear(); persister.save(blocks); } } + public synchronized void appendData(T data) { + pendingMessages.add(data); + } + public synchronized String getPrefix() { return Stream.iterate("0", x -> "0") .limit(prefixLength) .reduce("", (x, y) -> x + y); } + public synchronized String pendingData() { + StringJoiner stringJoiner = new StringJoiner("\n"); + if (pendingMessages.isEmpty()) { + return "no messages"; + } + pendingMessages.stream() + .map(dataFormatter::format) + .forEach(stringJoiner::add); + return stringJoiner.toString(); + } + public synchronized Block tail() { return blocks.get(blocks.size() - 1); } @@ -66,19 +93,22 @@ private void outputStats(Block newBlock) { System.out.printf("Timestamp: %s\n", newBlock.getTimestamp()); System.out.printf("Magic number: %s\n", newBlock.getMagicNumber()); System.out.printf("Hash of the previous block:\n%s\n", newBlock.getHashPreviousBlock()); - System.out.printf("Hash of the block: \n%s\n", newBlock.getHash()); + System.out.printf("Block data: \n%s\n", newBlock.getData()); + System.out.printf("Magic number: %s\n", newBlock.getMagicNumber()); System.out - .printf("Block was generating for: %s seconds\n", newBlock.getGenerationTime() / 1000); + .printf("Block was generating for: %s milliseconds\n", newBlock.getGenerationTime()); } private void adjustComplexity(Block newBlock) { if (!withinAcceptable(newBlock.getGenerationTime())) { if (ACCEPTABLE_TIME < newBlock.getGenerationTime()) { + if (prefixLength - 1 >= 0) { + prefixLength--; + } System.out.printf("N was decreased to %s\n\n", prefixLength); - prefixLength--; } else { - System.out.printf("N was increased to %s\n\n", prefixLength); prefixLength++; + System.out.printf("N was increased to %s\n\n", prefixLength); } } else { System.out.printf("N stays the same\n\n"); @@ -86,7 +116,7 @@ private void adjustComplexity(Block newBlock) { } private boolean withinAcceptable(Long generationTime) { - return Math.abs(generationTime - ACCEPTABLE_TIME) <= 1000; + return Math.abs(generationTime - ACCEPTABLE_TIME) <= 500; } private boolean validateAllChain(List blocks) { diff --git a/src/blockchain/Main.java b/src/blockchain/Main.java index 023ec28..ff0f84a 100644 --- a/src/blockchain/Main.java +++ b/src/blockchain/Main.java @@ -1,25 +1,42 @@ package blockchain; +import blockchain.data.Message; +import blockchain.data.format.PlanMessageFormat; +import blockchain.io.FilePersister; +import blockchain.io.Persister; import blockchain.miners.Miner; public class Main { - public static void main(String[] args) throws InterruptedException { + private static final String OUTPUT_FILE_NAME = "blockchain.ser"; + private static final int NUMBER_OF_MINERS = 10; - int NUMBER_OF_MINERS = 10; + public static void main(String[] args) throws InterruptedException { - Persister persister = new FilePersister("blockchain.ser"); - BlockGenerator blockGenerator = new BlockGenerator(); - Blockchain blockchain = new Blockchain(persister, blockGenerator); + Persister persister = new FilePersister(OUTPUT_FILE_NAME); + Blockchain blockchain = new Blockchain<>(persister, + new PlanMessageFormat()); Thread[] miners = new Thread[NUMBER_OF_MINERS]; + for (int i = 0; i < NUMBER_OF_MINERS; i++) { - miners[i] = new Thread(new Miner(blockchain, blockGenerator, Long.valueOf(i))); + miners[i] = new Thread(new Miner(blockchain, Long.valueOf(i))); } for (Thread miner : miners) { miner.start(); } - Thread.sleep(10000); + Thread.sleep(1000); + blockchain.appendData(new Message("Tom", "Hey, I'm first!")); + blockchain.appendData(new Message("Sarah", "It's not fair!")); + Thread.sleep(2000); + blockchain.appendData( + new Message("Sarah", "You always will be first because it is your blockchain!")); + Thread.sleep(1000); + blockchain.appendData(new Message("Sarah", "Anyway, thank you for this amazing chat.")); + Thread.sleep(2000); + blockchain.appendData(new Message("Tom", "You're welcome :)")); + blockchain.appendData(new Message("Nick", "Hey Tom, nice chat")); + for (Thread miner : miners) { miner.interrupt(); } diff --git a/src/blockchain/data/Message.java b/src/blockchain/data/Message.java new file mode 100644 index 0000000..9ff6951 --- /dev/null +++ b/src/blockchain/data/Message.java @@ -0,0 +1,20 @@ +package blockchain.data; + +public class Message { + + private String author; + private String text; + + public Message(String author, String text) { + this.author = author; + this.text = text; + } + + public String getAuthor() { + return author; + } + + public String getText() { + return text; + } +} diff --git a/src/blockchain/data/format/DataFormatter.java b/src/blockchain/data/format/DataFormatter.java new file mode 100644 index 0000000..0945a27 --- /dev/null +++ b/src/blockchain/data/format/DataFormatter.java @@ -0,0 +1,6 @@ +package blockchain.data.format; + +public interface DataFormatter { + + String format(T message); +} diff --git a/src/blockchain/data/format/PlanMessageFormat.java b/src/blockchain/data/format/PlanMessageFormat.java new file mode 100644 index 0000000..773b0fb --- /dev/null +++ b/src/blockchain/data/format/PlanMessageFormat.java @@ -0,0 +1,11 @@ +package blockchain.data.format; + +import blockchain.data.Message; + +public class PlanMessageFormat implements DataFormatter { + + @Override + public String format(Message message) { + return String.format("%s: %s", message.getAuthor(), message.getText()); + } +} diff --git a/src/blockchain/FilePersister.java b/src/blockchain/io/FilePersister.java similarity index 92% rename from src/blockchain/FilePersister.java rename to src/blockchain/io/FilePersister.java index 2b08d9e..c996c9a 100644 --- a/src/blockchain/FilePersister.java +++ b/src/blockchain/io/FilePersister.java @@ -1,5 +1,6 @@ -package blockchain; +package blockchain.io; +import blockchain.Block; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; @@ -9,7 +10,7 @@ import java.nio.file.Paths; import java.util.List; -class FilePersister implements Persister { +public class FilePersister implements Persister { private final String filename; diff --git a/src/blockchain/Persister.java b/src/blockchain/io/Persister.java similarity index 71% rename from src/blockchain/Persister.java rename to src/blockchain/io/Persister.java index fe58027..1004ad2 100644 --- a/src/blockchain/Persister.java +++ b/src/blockchain/io/Persister.java @@ -1,5 +1,6 @@ -package blockchain; +package blockchain.io; +import blockchain.Block; import java.util.List; public interface Persister { diff --git a/src/blockchain/miners/Miner.java b/src/blockchain/miners/Miner.java index 2c1dca2..22bb6e0 100644 --- a/src/blockchain/miners/Miner.java +++ b/src/blockchain/miners/Miner.java @@ -1,27 +1,42 @@ package blockchain.miners; import blockchain.Block; -import blockchain.BlockGenerator; import blockchain.Blockchain; +import blockchain.utils.StringUtil; +import java.util.Random; public class Miner implements Runnable { - private final Blockchain blockchain; - private BlockGenerator blockGenerator; + private final Blockchain blockchain; private final Long id; + private final Random random; - public Miner(Blockchain blockchain, BlockGenerator blockGenerator, Long id) { + public Miner(Blockchain blockchain, Long id) { this.blockchain = blockchain; - this.blockGenerator = blockGenerator; this.id = id; + this.random = new Random(); } @Override public void run() { while (!Thread.currentThread().isInterrupted()) { - Block prev = blockchain.tail(); - blockchain.accept(blockGenerator.generateNextBlock(prev.getId() + 1, id, - blockchain.getPrefix(), prev.getHash(), id.toString())); + long timestamp = System.currentTimeMillis(); + Block prev; + String prefix; + String data; + Integer magicNumber; + String hash; + do { + prev = blockchain.tail(); + prefix = blockchain.getPrefix(); + data = blockchain.pendingData(); + magicNumber = random.nextInt(); + hash = StringUtil.applySha256(prev.getHash() + magicNumber + data); + } while (!hash.startsWith(prefix)); + Block newBlock = new Block(prev.getId() + 1, id, prev.getHash(), hash, data, timestamp, + magicNumber, + System.currentTimeMillis() - timestamp); + blockchain.accept(newBlock); } } } diff --git a/src/blockchain/StringUtil.java b/src/blockchain/utils/StringUtil.java similarity index 96% rename from src/blockchain/StringUtil.java rename to src/blockchain/utils/StringUtil.java index 9a4c32d..b883ebd 100644 --- a/src/blockchain/StringUtil.java +++ b/src/blockchain/utils/StringUtil.java @@ -1,4 +1,4 @@ -package blockchain; +package blockchain.utils; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; From 7daf19759900cada3ac7349bbd2e2cf5b658dde0 Mon Sep 17 00:00:00 2001 From: oleksii Date: Mon, 10 Dec 2018 00:29:31 +0200 Subject: [PATCH 06/14] Fifth phase implementation done --- keys/privateKey | Bin 0 -> 635 bytes keys/publicKey | Bin 0 -> 162 bytes src/blockchain/Block.java | 8 +++- src/blockchain/Blockchain.java | 28 +++++++++--- src/blockchain/Main.java | 17 ++++--- src/blockchain/crypto/Sign.java | 27 ++++++++++++ src/blockchain/crypto/SignValidator.java | 27 ++++++++++++ src/blockchain/miners/Miner.java | 20 ++++++--- src/crypto/KeyPairGen.java | 54 +++++++++++++++++++++++ 9 files changed, 164 insertions(+), 17 deletions(-) create mode 100644 keys/privateKey create mode 100644 keys/publicKey create mode 100644 src/blockchain/crypto/Sign.java create mode 100644 src/blockchain/crypto/SignValidator.java create mode 100644 src/crypto/KeyPairGen.java diff --git a/keys/privateKey b/keys/privateKey new file mode 100644 index 0000000000000000000000000000000000000000..896c026fd546c28bdadd6988df3e703dc2c3b7f0 GIT binary patch literal 635 zcmV->0)+iAf&zB}0RS)!1_>&LNQUpVK9OMT>=3B0)c@5h`hzXB`j1T zf=Wil4Pyq!ukH!)$iQ|wxCHS)cz(Smw+>a=RXJ>Ji}OC;HK(W`K;Nw%Z!l9@j62zz zu)VnSz9j=10d0^h*s0-pWA5~!{HT0GhY?509K$^UmkSI6|TlN@N_1B+Qv)y_J%DMynpby5epFC$u;YuFqE z80_&Bs?fL^!O~V*Zy5MzO!x>^!1!j&gL{o*>9bB+T>Dwy5?Lc`WDEzLt6|1L0zm-c zCLtrdP5EWf=t_)%^moGR!c>ab;!qCxdYYq9{pRo2sb6ZU&J>({28(V`PZ{AO{-Z&eP?9(DLH6UIXQCw!RkFtYc=K4oe(ITfbDCUa z106q;tC(}Em*hLc@@;N6?mGRd?fglM=|uuT0LwrFrq6Y;0Y2}n7_Kj#cT`rIj}x#8 z&H9j|Y{tC%l5yN>!aj29`CUq}7Lf5Nq0#bwFu@(PEV;qW1}$arbpk*){n%1jLFWtc zU*BkTI(jHDAxAsCLN(It?xq0=JDMYn5579NZzw%IQBuNO9amkYO!amqXRi{$&O$bS z_^d4gK>&mlR9y9pQ3)104`@o|uQ64g20N@e2sGL4;>pUYK2l?QfNw7)2C)z=mh}H` V;j=T|H0|6bxU83Aa66r>Lq*w$CE)-7 literal 0 HcmV?d00001 diff --git a/keys/publicKey b/keys/publicKey new file mode 100644 index 0000000000000000000000000000000000000000..7c55adb3439fdf5bdb16e336db14c109f49b1ded GIT binary patch literal 162 zcmV;T0A2qufuAr91_>&LNQUa=RXJ>Ji}OC;HK(W`K;Nw%Z!l9@j62zzu)VnSz9j=1 z0d0^h*s0-pWA5~!{HT0GhY { private int prefixLength; private final Persister persister; private DataFormatter dataFormatter; + private SignValidator signValidator; public Blockchain(Persister persister, - DataFormatter dataFormatter) { + DataFormatter dataFormatter, SignValidator signValidator) { this.persister = persister; this.dataFormatter = dataFormatter; + this.signValidator = signValidator; this.prefixLength = 0; List blocks = persister.load(); if (!blocks.isEmpty() && validateAllChain(blocks)) { @@ -32,7 +36,7 @@ public Blockchain(Persister persister, this.blocks = new LinkedList<>(Collections.singleton( new Block(0, 0L, "0", StringUtil.applySha256("0" + 0), "no messages", - System.currentTimeMillis(), 0, 0L))); + null, System.currentTimeMillis(), 0, 0L))); persister.save(blocks); } pendingMessages = new LinkedList<>(); @@ -73,14 +77,28 @@ public synchronized Block tail() { } private boolean isValid(Block newBlock) { - Block tailBlock = blocks.get(blocks.size() - 1); - if (!newBlock.getHash().startsWith(getPrefix()) || !newBlock.getHashPreviousBlock() - .equals(tailBlock.getHash())) { + try { + Block tailBlock = blocks.get(blocks.size() - 1); + if (isBlocksNotRelatedOrLowProof(newBlock, tailBlock) || + !isMessageSigned(newBlock)) { + return false; + } + } catch (Exception e) { return false; } return true; } + private boolean isMessageSigned(Block newBlock) throws Exception { + return signValidator.verifySignature(newBlock.getData().getBytes(StandardCharsets.UTF_8), + newBlock.getDataSignature()); + } + + private boolean isBlocksNotRelatedOrLowProof(Block newBlock, Block tailBlock) { + return !newBlock.getHash().startsWith(getPrefix()) || !newBlock.getHashPreviousBlock() + .equals(tailBlock.getHash()); + } + private void outputAndAdjust(Block newBlock) { outputStats(newBlock); adjustComplexity(newBlock); diff --git a/src/blockchain/Main.java b/src/blockchain/Main.java index ff0f84a..1493a79 100644 --- a/src/blockchain/Main.java +++ b/src/blockchain/Main.java @@ -1,5 +1,7 @@ package blockchain; +import blockchain.crypto.Sign; +import blockchain.crypto.SignValidator; import blockchain.data.Message; import blockchain.data.format.PlanMessageFormat; import blockchain.io.FilePersister; @@ -11,20 +13,23 @@ public class Main { private static final String OUTPUT_FILE_NAME = "blockchain.ser"; private static final int NUMBER_OF_MINERS = 10; - public static void main(String[] args) throws InterruptedException { + public static void main(String[] args) throws Exception { + Sign sign = new Sign("keys/privateKey"); + SignValidator signValidator = new SignValidator("keys/publicKey"); Persister persister = new FilePersister(OUTPUT_FILE_NAME); Blockchain blockchain = new Blockchain<>(persister, - new PlanMessageFormat()); + new PlanMessageFormat(), signValidator); Thread[] miners = new Thread[NUMBER_OF_MINERS]; for (int i = 0; i < NUMBER_OF_MINERS; i++) { - miners[i] = new Thread(new Miner(blockchain, Long.valueOf(i))); + miners[i] = new Thread(new Miner(blockchain, Long.valueOf(i), sign)); } for (Thread miner : miners) { miner.start(); } + Thread.sleep(1000); blockchain.appendData(new Message("Tom", "Hey, I'm first!")); blockchain.appendData(new Message("Sarah", "It's not fair!")); @@ -34,8 +39,10 @@ public static void main(String[] args) throws InterruptedException { Thread.sleep(1000); blockchain.appendData(new Message("Sarah", "Anyway, thank you for this amazing chat.")); Thread.sleep(2000); - blockchain.appendData(new Message("Tom", "You're welcome :)")); - blockchain.appendData(new Message("Nick", "Hey Tom, nice chat")); + blockchain + .appendData(new Message("Tom", "You're welcome :)")); + blockchain.appendData( + new Message("Nick", "Hey Tom, nice chat")); for (Thread miner : miners) { miner.interrupt(); diff --git a/src/blockchain/crypto/Sign.java b/src/blockchain/crypto/Sign.java new file mode 100644 index 0000000..0559bcf --- /dev/null +++ b/src/blockchain/crypto/Sign.java @@ -0,0 +1,27 @@ +package blockchain.crypto; + +import java.io.File; +import java.nio.file.Files; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.Signature; +import java.security.spec.PKCS8EncodedKeySpec; + +public class Sign { + + private PrivateKey privateKey; + + public Sign(String privateKeyPath) throws Exception { + byte[] keyBytes = Files.readAllBytes(new File(privateKeyPath).toPath()); + PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory kf = KeyFactory.getInstance("RSA"); + this.privateKey = kf.generatePrivate(spec); + } + + public byte[] sign(String text) throws Exception { + Signature rsa = Signature.getInstance("SHA1withRSA"); + rsa.initSign(privateKey); + rsa.update(text.getBytes()); + return rsa.sign(); + } +} diff --git a/src/blockchain/crypto/SignValidator.java b/src/blockchain/crypto/SignValidator.java new file mode 100644 index 0000000..8c17ffb --- /dev/null +++ b/src/blockchain/crypto/SignValidator.java @@ -0,0 +1,27 @@ +package blockchain.crypto; + +import java.io.File; +import java.nio.file.Files; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.Signature; +import java.security.spec.X509EncodedKeySpec; + +public class SignValidator { + + private PublicKey publicKey; + + public SignValidator(String publicKeyFilePath) throws Exception { + byte[] keyBytes = Files.readAllBytes(new File(publicKeyFilePath).toPath()); + X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes); + KeyFactory kf = KeyFactory.getInstance("RSA"); + publicKey = kf.generatePublic(spec); + } + + public boolean verifySignature(byte[] data, byte[] signature) throws Exception { + Signature sig = Signature.getInstance("SHA1withRSA"); + sig.initVerify(publicKey); + sig.update(data); + return sig.verify(signature); + } +} diff --git a/src/blockchain/miners/Miner.java b/src/blockchain/miners/Miner.java index 22bb6e0..589475f 100644 --- a/src/blockchain/miners/Miner.java +++ b/src/blockchain/miners/Miner.java @@ -2,6 +2,7 @@ import blockchain.Block; import blockchain.Blockchain; +import blockchain.crypto.Sign; import blockchain.utils.StringUtil; import java.util.Random; @@ -9,11 +10,13 @@ public class Miner implements Runnable { private final Blockchain blockchain; private final Long id; + private Sign sign; private final Random random; - public Miner(Blockchain blockchain, Long id) { + public Miner(Blockchain blockchain, Long id, Sign sign) { this.blockchain = blockchain; this.id = id; + this.sign = sign; this.random = new Random(); } @@ -24,18 +27,23 @@ public void run() { Block prev; String prefix; String data; - Integer magicNumber; + int magicNumber; String hash; do { prev = blockchain.tail(); prefix = blockchain.getPrefix(); data = blockchain.pendingData(); magicNumber = random.nextInt(); - hash = StringUtil.applySha256(prev.getHash() + magicNumber + data); + String dataUsedForHash = prev.getHash() + magicNumber + data; + hash = StringUtil.applySha256(dataUsedForHash); } while (!hash.startsWith(prefix)); - Block newBlock = new Block(prev.getId() + 1, id, prev.getHash(), hash, data, timestamp, - magicNumber, - System.currentTimeMillis() - timestamp); + Block newBlock = null; + try { + newBlock = new Block(prev.getId() + 1, id, prev.getHash(), hash, data, + sign.sign(data), timestamp, magicNumber, System.currentTimeMillis() - timestamp); + } catch (Exception e) { + Thread.currentThread().interrupt(); + } blockchain.accept(newBlock); } } diff --git a/src/crypto/KeyPairGen.java b/src/crypto/KeyPairGen.java new file mode 100644 index 0000000..59442bf --- /dev/null +++ b/src/crypto/KeyPairGen.java @@ -0,0 +1,54 @@ +package crypto; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; + +public class KeyPairGen { + + private KeyPairGenerator keyGen; + private KeyPair pair; + private PrivateKey privateKey; + private PublicKey publicKey; + + public KeyPairGen(int keyLen) throws NoSuchAlgorithmException { + this.keyGen = keyGen.getInstance("RSA"); + this.keyGen.initialize(keyLen); + } + + + public void genPair() { + pair = keyGen.generateKeyPair(); + privateKey = pair.getPrivate(); + publicKey = pair.getPublic(); + } + + public PrivateKey getPrivateKey() { + return privateKey; + } + + public PublicKey getPublicKey() { + return publicKey; + } + + public static void main(String[] args) throws Exception { + KeyPairGen gen = new KeyPairGen(1024); + gen.genPair(); + writeToFile("keys/publicKey", gen.getPublicKey().getEncoded()); + writeToFile("keys/privateKey", gen.getPrivateKey().getEncoded()); + } + + private static void writeToFile(String path, byte[] key) throws IOException { + File f = new File(path); + f.getParentFile().mkdirs(); + FileOutputStream fos = new FileOutputStream(f); + fos.write(key); + fos.flush(); + fos.close(); + } +} From 3ae01f1a060ce5bc3c85fe09808c87fd1f205f8e Mon Sep 17 00:00:00 2001 From: oleksii Date: Mon, 10 Dec 2018 14:23:33 +0200 Subject: [PATCH 07/14] Fifth phase implementation done --- src/crypto/KeyPairGen.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/crypto/KeyPairGen.java b/src/crypto/KeyPairGen.java index 59442bf..1bd266a 100644 --- a/src/crypto/KeyPairGen.java +++ b/src/crypto/KeyPairGen.java @@ -16,29 +16,31 @@ public class KeyPairGen { private PrivateKey privateKey; private PublicKey publicKey; - public KeyPairGen(int keyLen) throws NoSuchAlgorithmException { - this.keyGen = keyGen.getInstance("RSA"); + private KeyPairGen(int keyLen) throws NoSuchAlgorithmException { + this.keyGen = KeyPairGenerator.getInstance("RSA"); this.keyGen.initialize(keyLen); } - public void genPair() { + private void genPair() { pair = keyGen.generateKeyPair(); privateKey = pair.getPrivate(); publicKey = pair.getPublic(); } - public PrivateKey getPrivateKey() { + private PrivateKey getPrivateKey() { return privateKey; } - public PublicKey getPublicKey() { + private PublicKey getPublicKey() { return publicKey; } public static void main(String[] args) throws Exception { KeyPairGen gen = new KeyPairGen(1024); + gen.genPair(); + writeToFile("keys/publicKey", gen.getPublicKey().getEncoded()); writeToFile("keys/privateKey", gen.getPrivateKey().getEncoded()); } From 7bc0f21aba3bc1f5ba8c01cc73b00347f2c9e827 Mon Sep 17 00:00:00 2001 From: oleksii Date: Wed, 19 Dec 2018 01:15:15 +0200 Subject: [PATCH 08/14] fourth phase implementation done --- src/blockchain/Block.java | 17 +++++++---------- src/blockchain/Blockchain.java | 28 +++++++++++++++------------- src/blockchain/Main.java | 2 ++ src/blockchain/miners/Miner.java | 31 ++++++++++++++----------------- 4 files changed, 38 insertions(+), 40 deletions(-) diff --git a/src/blockchain/Block.java b/src/blockchain/Block.java index 7aee5e9..fdf19dc 100644 --- a/src/blockchain/Block.java +++ b/src/blockchain/Block.java @@ -8,22 +8,19 @@ public class Block implements Serializable { private final Long minerId; private final String hashPreviousBlock; private final String hash; - private final String data; private final Long timestamp; private final Integer magicNumber; - private final Long generationTime; + private String data; - public Block(Integer id, Long minerId, String hashPreviousBlock, String hash, - String data, Long timestamp, Integer magicNumber, Long generationTime) { + public Block(Integer id, Long minerId, String hashPreviousBlock, String hash, Long timestamp, + Integer magicNumber) { this.id = id; this.minerId = minerId; this.hashPreviousBlock = hashPreviousBlock; this.hash = hash; - this.data = data; this.timestamp = timestamp; this.magicNumber = magicNumber; - this.generationTime = generationTime; } public Integer getId() { @@ -46,12 +43,12 @@ public String getData() { return data; } - public Integer getMagicNumber() { - return magicNumber; + public void setData(String data) { + this.data = data; } - public Long getGenerationTime() { - return generationTime; + public Integer getMagicNumber() { + return magicNumber; } public Long getMinerId() { diff --git a/src/blockchain/Blockchain.java b/src/blockchain/Blockchain.java index ca9c04f..f4e0a99 100644 --- a/src/blockchain/Blockchain.java +++ b/src/blockchain/Blockchain.java @@ -2,8 +2,8 @@ import blockchain.data.format.DataFormatter; import blockchain.io.Persister; -import blockchain.utils.StringUtil; import java.util.Collections; +import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.StringJoiner; @@ -12,7 +12,7 @@ public class Blockchain { - private static final Long ACCEPTABLE_TIME = TimeUnit.SECONDS.toMillis(3); + private static final Long ACCEPTABLE_TIME = TimeUnit.SECONDS.toMillis(5); private final List blocks; private final List pendingMessages; @@ -20,8 +20,7 @@ public class Blockchain { private final Persister persister; private DataFormatter dataFormatter; - public Blockchain(Persister persister, - DataFormatter dataFormatter) { + public Blockchain(Persister persister, DataFormatter dataFormatter) { this.persister = persister; this.dataFormatter = dataFormatter; this.prefixLength = 0; @@ -30,9 +29,7 @@ public Blockchain(Persister persister, this.blocks = blocks; } else { this.blocks = new LinkedList<>(Collections.singleton( - new Block(0, 0L, "0", StringUtil.applySha256("0" + 0), - "no messages", - System.currentTimeMillis(), 0, 0L))); + new Block(1, 0L, "", "0", new Date().getTime(), 0))); persister.save(blocks); } pendingMessages = new LinkedList<>(); @@ -40,6 +37,7 @@ public Blockchain(Persister persister, public synchronized void accept(Block newBlock) { if (isValid(newBlock)) { + newBlock.setData(pendingData()); outputAndAdjust(newBlock); blocks.add(newBlock); pendingMessages.clear(); @@ -96,13 +94,13 @@ private void outputStats(Block newBlock) { System.out.printf("Block data: \n%s\n", newBlock.getData()); System.out.printf("Magic number: %s\n", newBlock.getMagicNumber()); System.out - .printf("Block was generating for: %s milliseconds\n", newBlock.getGenerationTime()); + .printf("Block was generating for: %s seconds\n", getGenerationTime(newBlock) / 1000); } private void adjustComplexity(Block newBlock) { - if (!withinAcceptable(newBlock.getGenerationTime())) { - if (ACCEPTABLE_TIME < newBlock.getGenerationTime()) { - if (prefixLength - 1 >= 0) { + if (!withinAcceptable(newBlock)) { + if (ACCEPTABLE_TIME < getGenerationTime(newBlock)) { + if (prefixLength > 0) { prefixLength--; } System.out.printf("N was decreased to %s\n\n", prefixLength); @@ -115,8 +113,12 @@ private void adjustComplexity(Block newBlock) { } } - private boolean withinAcceptable(Long generationTime) { - return Math.abs(generationTime - ACCEPTABLE_TIME) <= 500; + private Long getGenerationTime(Block newBlock) { + return tail() == null ? 0 : (newBlock.getTimestamp() - tail().getTimestamp()); + } + + private boolean withinAcceptable(Block newBlock) { + return Math.abs(getGenerationTime(newBlock) - ACCEPTABLE_TIME) <= 1000; } private boolean validateAllChain(List blocks) { diff --git a/src/blockchain/Main.java b/src/blockchain/Main.java index ff0f84a..38ca20f 100644 --- a/src/blockchain/Main.java +++ b/src/blockchain/Main.java @@ -22,9 +22,11 @@ public static void main(String[] args) throws InterruptedException { for (int i = 0; i < NUMBER_OF_MINERS; i++) { miners[i] = new Thread(new Miner(blockchain, Long.valueOf(i))); } + for (Thread miner : miners) { miner.start(); } + Thread.sleep(1000); blockchain.appendData(new Message("Tom", "Hey, I'm first!")); blockchain.appendData(new Message("Sarah", "It's not fair!")); diff --git a/src/blockchain/miners/Miner.java b/src/blockchain/miners/Miner.java index 22bb6e0..d433eed 100644 --- a/src/blockchain/miners/Miner.java +++ b/src/blockchain/miners/Miner.java @@ -20,23 +20,20 @@ public Miner(Blockchain blockchain, Long id) { @Override public void run() { while (!Thread.currentThread().isInterrupted()) { - long timestamp = System.currentTimeMillis(); - Block prev; - String prefix; - String data; - Integer magicNumber; - String hash; - do { - prev = blockchain.tail(); - prefix = blockchain.getPrefix(); - data = blockchain.pendingData(); - magicNumber = random.nextInt(); - hash = StringUtil.applySha256(prev.getHash() + magicNumber + data); - } while (!hash.startsWith(prefix)); - Block newBlock = new Block(prev.getId() + 1, id, prev.getHash(), hash, data, timestamp, - magicNumber, - System.currentTimeMillis() - timestamp); - blockchain.accept(newBlock); + Block prev = blockchain.tail(); + blockchain.accept(generateNextBlock(prev.getId() + 1, id, prev.getHash())); } } + + private Block generateNextBlock(int id, long minerId, String previousHash) { + Integer magicNumber; + String hash; + String prefix; + do { + prefix = blockchain.getPrefix(); + magicNumber = random.nextInt(); + hash = StringUtil.applySha256(previousHash + magicNumber); + } while (!hash.startsWith(prefix)); + return new Block(id, minerId, previousHash, hash, System.currentTimeMillis(), magicNumber); + } } From bcc9223e1e2dda602bf8165df885646b9a9dab86 Mon Sep 17 00:00:00 2001 From: oleksii Date: Fri, 28 Dec 2018 00:56:35 +0200 Subject: [PATCH 09/14] fifth phase implementation done --- src/blockchain/Block.java | 4 ++-- src/blockchain/Blockchain.java | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/blockchain/Block.java b/src/blockchain/Block.java index 17706f3..11a3d0b 100644 --- a/src/blockchain/Block.java +++ b/src/blockchain/Block.java @@ -8,8 +8,8 @@ public class Block implements Serializable { private final Long minerId; private final String hashPreviousBlock; private final String hash; - private final String data; - private final byte[] dataSignature; + private String data; + private byte[] dataSignature; private final Long timestamp; private final Integer magicNumber; private final Long generationTime; diff --git a/src/blockchain/Blockchain.java b/src/blockchain/Blockchain.java index 798f631..61d5fe1 100644 --- a/src/blockchain/Blockchain.java +++ b/src/blockchain/Blockchain.java @@ -22,9 +22,10 @@ public class Blockchain { private final Persister persister; private DataFormatter dataFormatter; private SignValidator signValidator; + private long uniqueId = 1; - public Blockchain(Persister persister, - DataFormatter dataFormatter, SignValidator signValidator) { + public Blockchain(Persister persister, DataFormatter dataFormatter, + SignValidator signValidator) { this.persister = persister; this.dataFormatter = dataFormatter; this.signValidator = signValidator; @@ -76,6 +77,10 @@ public synchronized Block tail() { return blocks.get(blocks.size() - 1); } + public synchronized long incrementAndGetUniqueId() { + return uniqueId++; + } + private boolean isValid(Block newBlock) { try { Block tailBlock = blocks.get(blocks.size() - 1); From ae3b3ab6fee01b935eb4f992f961e7ca6b33226f Mon Sep 17 00:00:00 2001 From: oleksii Date: Sat, 29 Dec 2018 13:51:56 +0200 Subject: [PATCH 10/14] fourth phase implementation done code review --- src/blockchain/Block.java | 3 +- src/blockchain/BlockBuilder.java | 51 ++++++++++++++++ src/blockchain/Blockchain.java | 101 ++++++++++++++++++++++--------- src/blockchain/Main.java | 22 +++---- src/blockchain/data/Message.java | 4 +- src/blockchain/miners/Miner.java | 11 +++- 6 files changed, 149 insertions(+), 43 deletions(-) create mode 100644 src/blockchain/BlockBuilder.java diff --git a/src/blockchain/Block.java b/src/blockchain/Block.java index fdf19dc..dcc4636 100644 --- a/src/blockchain/Block.java +++ b/src/blockchain/Block.java @@ -14,13 +14,14 @@ public class Block implements Serializable { public Block(Integer id, Long minerId, String hashPreviousBlock, String hash, Long timestamp, - Integer magicNumber) { + Integer magicNumber, String data) { this.id = id; this.minerId = minerId; this.hashPreviousBlock = hashPreviousBlock; this.hash = hash; this.timestamp = timestamp; this.magicNumber = magicNumber; + this.data = data; } public Integer getId() { diff --git a/src/blockchain/BlockBuilder.java b/src/blockchain/BlockBuilder.java new file mode 100644 index 0000000..890f59a --- /dev/null +++ b/src/blockchain/BlockBuilder.java @@ -0,0 +1,51 @@ +package blockchain; + +public class BlockBuilder { + + private Integer id; + private Long minerId; + private String hashPreviousBlock; + private String hash; + private Long timestamp; + private Integer magicNumber; + private String data; + + public BlockBuilder withId(Integer id) { + this.id = id; + return this; + } + + public BlockBuilder withMinerId(Long minerId) { + this.minerId = minerId; + return this; + } + + public BlockBuilder withHashPreviousBlock(String hashPreviousBlock) { + this.hashPreviousBlock = hashPreviousBlock; + return this; + } + + public BlockBuilder withHash(String hash) { + this.hash = hash; + return this; + } + + public BlockBuilder withTimestamp(Long timestamp) { + this.timestamp = timestamp; + return this; + } + + public BlockBuilder withMagicNumber(Integer magicNumber) { + this.magicNumber = magicNumber; + return this; + } + + public BlockBuilder withData(String data) { + this.data = data; + return this; + } + + public Block build() { + return new Block(id, minerId, hashPreviousBlock, hash, timestamp, magicNumber, data); + } +} diff --git a/src/blockchain/Blockchain.java b/src/blockchain/Blockchain.java index f4e0a99..bf83119 100644 --- a/src/blockchain/Blockchain.java +++ b/src/blockchain/Blockchain.java @@ -6,21 +6,29 @@ import java.util.Date; import java.util.LinkedList; import java.util.List; +import java.util.Objects; import java.util.StringJoiner; import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; import java.util.stream.Stream; public class Blockchain { private static final Long ACCEPTABLE_TIME = TimeUnit.SECONDS.toMillis(5); + private final ReentrantReadWriteLock readWritelock; + private final List blocks; private final List pendingMessages; - private int prefixLength; private final Persister persister; + + private int prefixLength; private DataFormatter dataFormatter; public Blockchain(Persister persister, DataFormatter dataFormatter) { + this.readWritelock = new ReentrantReadWriteLock(); this.persister = persister; this.dataFormatter = dataFormatter; this.prefixLength = 0; @@ -28,52 +36,89 @@ public Blockchain(Persister persister, DataFormatter dataFormatter) { if (!blocks.isEmpty() && validateAllChain(blocks)) { this.blocks = blocks; } else { + new BlockBuilder().build(); this.blocks = new LinkedList<>(Collections.singleton( - new Block(1, 0L, "", "0", new Date().getTime(), 0))); + new BlockBuilder() + .withId(1) + .withMinerId(0L) + .withHashPreviousBlock("") + .withHash("0") + .withMagicNumber(0) + .withTimestamp(new Date().getTime()).build())); persister.save(blocks); } pendingMessages = new LinkedList<>(); } - public synchronized void accept(Block newBlock) { - if (isValid(newBlock)) { - newBlock.setData(pendingData()); - outputAndAdjust(newBlock); - blocks.add(newBlock); - pendingMessages.clear(); - persister.save(blocks); + public void accept(Block newBlock) { + WriteLock writeLock = readWritelock.writeLock(); + try { + writeLock.lock(); + if (isValid(newBlock)) { + newBlock.setData(pendingData()); + outputAndAdjust(newBlock); + blocks.add(newBlock); + pendingMessages.clear(); + persister.save(blocks); + } + } finally { + writeLock.unlock(); } } - public synchronized void appendData(T data) { - pendingMessages.add(data); + public void appendData(T data) { + WriteLock writeLock = readWritelock.writeLock(); + try { + writeLock.lock(); + pendingMessages.add(data); + } finally { + writeLock.unlock(); + } } - public synchronized String getPrefix() { - return Stream.iterate("0", x -> "0") - .limit(prefixLength) - .reduce("", (x, y) -> x + y); + public String getPrefix() { + ReadLock readLock = readWritelock.readLock(); + try { + readLock.lock(); + return Stream.iterate("0", x -> "0") + .limit(prefixLength) + .reduce("", (x, y) -> x + y); + } finally { + readLock.unlock(); + } } - public synchronized String pendingData() { - StringJoiner stringJoiner = new StringJoiner("\n"); - if (pendingMessages.isEmpty()) { - return "no messages"; + public String pendingData() { + ReadLock readLock = readWritelock.readLock(); + try { + readLock.lock(); + StringJoiner stringJoiner = new StringJoiner("\n"); + if (pendingMessages.isEmpty()) { + return "no messages"; + } + pendingMessages.stream() + .map(dataFormatter::format) + .forEach(stringJoiner::add); + return stringJoiner.toString(); + } finally { + readLock.unlock(); } - pendingMessages.stream() - .map(dataFormatter::format) - .forEach(stringJoiner::add); - return stringJoiner.toString(); } - public synchronized Block tail() { - return blocks.get(blocks.size() - 1); + public Block tail() { + ReadLock readLock = readWritelock.readLock(); + try { + readLock.lock(); + return blocks.get(blocks.size() - 1); + } finally { + readLock.unlock(); + } } private boolean isValid(Block newBlock) { Block tailBlock = blocks.get(blocks.size() - 1); - if (!newBlock.getHash().startsWith(getPrefix()) || !newBlock.getHashPreviousBlock() - .equals(tailBlock.getHash())) { + if (!newBlock.getHash().startsWith(getPrefix()) || !Objects + .equals(newBlock.getHashPreviousBlock(), tailBlock.getHash())) { return false; } return true; @@ -125,7 +170,7 @@ private boolean validateAllChain(List blocks) { for (int i = 1; i < blocks.size(); i++) { Block prev = blocks.get(i - 1); Block cur = blocks.get(i); - if (!cur.getHashPreviousBlock().equals(prev.getHash())) { + if (!Objects.equals(prev.getHash(), cur.getHashPreviousBlock())) { return false; } } diff --git a/src/blockchain/Main.java b/src/blockchain/Main.java index 38ca20f..a2b2b57 100644 --- a/src/blockchain/Main.java +++ b/src/blockchain/Main.java @@ -5,11 +5,16 @@ import blockchain.io.FilePersister; import blockchain.io.Persister; import blockchain.miners.Miner; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class Main { private static final String OUTPUT_FILE_NAME = "blockchain.ser"; private static final int NUMBER_OF_MINERS = 10; + private static AtomicLong ids = new AtomicLong(1); public static void main(String[] args) throws InterruptedException { @@ -17,15 +22,12 @@ public static void main(String[] args) throws InterruptedException { Blockchain blockchain = new Blockchain<>(persister, new PlanMessageFormat()); - Thread[] miners = new Thread[NUMBER_OF_MINERS]; + List miners = Stream + .generate(() -> new Thread(new Miner(blockchain, ids.getAndIncrement()))) + .limit(NUMBER_OF_MINERS) + .collect(Collectors.toList()); - for (int i = 0; i < NUMBER_OF_MINERS; i++) { - miners[i] = new Thread(new Miner(blockchain, Long.valueOf(i))); - } - - for (Thread miner : miners) { - miner.start(); - } + miners.forEach(Thread::start); Thread.sleep(1000); blockchain.appendData(new Message("Tom", "Hey, I'm first!")); @@ -39,8 +41,6 @@ public static void main(String[] args) throws InterruptedException { blockchain.appendData(new Message("Tom", "You're welcome :)")); blockchain.appendData(new Message("Nick", "Hey Tom, nice chat")); - for (Thread miner : miners) { - miner.interrupt(); - } + miners.forEach(Thread::interrupt); } } \ No newline at end of file diff --git a/src/blockchain/data/Message.java b/src/blockchain/data/Message.java index 9ff6951..d1ecfeb 100644 --- a/src/blockchain/data/Message.java +++ b/src/blockchain/data/Message.java @@ -2,8 +2,8 @@ public class Message { - private String author; - private String text; + private final String author; + private final String text; public Message(String author, String text) { this.author = author; diff --git a/src/blockchain/miners/Miner.java b/src/blockchain/miners/Miner.java index d433eed..08bde3c 100644 --- a/src/blockchain/miners/Miner.java +++ b/src/blockchain/miners/Miner.java @@ -1,8 +1,10 @@ package blockchain.miners; import blockchain.Block; +import blockchain.BlockBuilder; import blockchain.Blockchain; import blockchain.utils.StringUtil; +import java.util.Date; import java.util.Random; public class Miner implements Runnable { @@ -26,6 +28,10 @@ public void run() { } private Block generateNextBlock(int id, long minerId, String previousHash) { + BlockBuilder blockBuilder = new BlockBuilder() + .withId(id) + .withMinerId(minerId) + .withHashPreviousBlock(previousHash); Integer magicNumber; String hash; String prefix; @@ -33,7 +39,10 @@ private Block generateNextBlock(int id, long minerId, String previousHash) { prefix = blockchain.getPrefix(); magicNumber = random.nextInt(); hash = StringUtil.applySha256(previousHash + magicNumber); + blockBuilder = blockBuilder.withHash(hash) + .withMagicNumber(magicNumber) + .withTimestamp(new Date().getTime()); } while (!hash.startsWith(prefix)); - return new Block(id, minerId, previousHash, hash, System.currentTimeMillis(), magicNumber); + return blockBuilder.build(); } } From 4a2da6102c7d73836403b676a710f3110406e9be Mon Sep 17 00:00:00 2001 From: oleksii Date: Sun, 30 Dec 2018 00:47:01 +0200 Subject: [PATCH 11/14] fourth phase implementation done code review --- keys/privateKey | Bin 635 -> 635 bytes keys/publicKey | Bin 162 -> 162 bytes src/blockchain/Block.java | 27 ++++---- src/blockchain/BlockBuilder.java | 29 +++++---- src/blockchain/Blockchain.java | 59 +++++++++++++----- src/blockchain/Main.java | 51 +++++++++------ src/blockchain/data/Message.java | 27 +++++++- src/blockchain/data/SignedData.java | 10 +++ ...ageFormat.java => PlainMessageFormat.java} | 2 +- src/blockchain/io/FilePersister.java | 10 +-- src/blockchain/io/Persister.java | 8 ++- src/blockchain/miners/Miner.java | 50 +++++++-------- src/crypto/KeyPairGen.java | 39 ++++++------ 13 files changed, 191 insertions(+), 121 deletions(-) create mode 100644 src/blockchain/data/SignedData.java rename src/blockchain/data/format/{PlanMessageFormat.java => PlainMessageFormat.java} (74%) diff --git a/keys/privateKey b/keys/privateKey index 896c026fd546c28bdadd6988df3e703dc2c3b7f0..d6b6bf7d97dc30d4d817d1f42be5a8385c46fe80 100644 GIT binary patch delta 609 zcmV-n0-pW*1p5S#C4ZbxUpu(n3HKWs^Y)9O4vpaDC%SwXh*ofaokw%|+V}l7E+mrb zr|jN9B{HHW|I4C4g*`Pm%KEL72}lAsTr4qUf1GTT{_B>CvoN$47H>y~85#n<8nd0oUFPZ3W+7;-&1L&yYk z=#9LInkiSDgyYs-{&+AHkU)C3J<~yp%PB{5-2oMo z)AC;yX;Y?Yfx7`PXN32%Pd+KSYT!uj-!X(GZmzx_l7H>q=uG&w&PdJM?5_VcuXpPL zK>(PQ{U`>7_MiM$%CnoLkp(_VoOvisD*FF-$)qF^+QhL2t*Ag`Zab;!qCxdYYq9 z{pRo2sb6ZU&J>({28(V`PZ{AO{-Z&eP?9(D zLH6UIXQCw!RkFtYc=K4oe(ITfbDCUa106q;tC(}Emw)6t!}4uzH|{$9s_pzqjOj%J zK>*7@1E$Y)u>n5stQf8@o_ADMnvWB(3eEbEqin{!`;u|oYQjEp>iJztu@;c=DWTEw zelWouv@E&7%?2%H@pS?~H~rXBSwZIu@n7F)bvk+|Fd;`fy+Sq8?Cz!k2|Jo2jSs#$ zxo;>vJz7yx!dx9!U8PL*b|z=962Z5Gu|}q+$OlJmtt@`ovcGe2=gQ5 diff --git a/keys/publicKey b/keys/publicKey index 7c55adb3439fdf5bdb16e336db14c109f49b1ded..3d0ed97a22a5de1128266658aac3049cb7ae3d59 100644 GIT binary patch delta 141 zcmV;80CNAL0ipqr9eCvoN$47*Ag`Z implements Serializable { private final Integer id; private final Long minerId; private final String hashPreviousBlock; private final String hash; - private String data; - private byte[] dataSignature; private final Long timestamp; private final Integer magicNumber; - private final Long generationTime; + private List data; - public Block(Integer id, Long minerId, String hashPreviousBlock, String hash, Long timestamp, - Integer magicNumber, String data) { public Block(Integer id, Long minerId, String hashPreviousBlock, String hash, - String data, byte[] dataSignature, Long timestamp, Integer magicNumber, Long generationTime) { + Long timestamp, Integer magicNumber) { this.id = id; this.minerId = minerId; this.hashPreviousBlock = hashPreviousBlock; this.hash = hash; - this.data = data; - this.dataSignature = dataSignature; this.timestamp = timestamp; this.magicNumber = magicNumber; - this.data = data; } public Integer getId() { @@ -46,10 +41,6 @@ public Long getTimestamp() { return timestamp; } - public String getData() { - return data; - } - public Integer getMagicNumber() { return magicNumber; } @@ -58,7 +49,11 @@ public Long getMinerId() { return minerId; } - public byte[] getDataSignature() { - return dataSignature; + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; } } diff --git a/src/blockchain/BlockBuilder.java b/src/blockchain/BlockBuilder.java index 890f59a..0306842 100644 --- a/src/blockchain/BlockBuilder.java +++ b/src/blockchain/BlockBuilder.java @@ -1,6 +1,9 @@ package blockchain; -public class BlockBuilder { +import blockchain.data.SignedData; +import java.io.Serializable; + +public class BlockBuilder { private Integer id; private Long minerId; @@ -9,43 +12,39 @@ public class BlockBuilder { private Long timestamp; private Integer magicNumber; private String data; + private byte[] dataSignature; - public BlockBuilder withId(Integer id) { + public BlockBuilder withId(Integer id) { this.id = id; return this; } - public BlockBuilder withMinerId(Long minerId) { + public BlockBuilder withMinerId(Long minerId) { this.minerId = minerId; return this; } - public BlockBuilder withHashPreviousBlock(String hashPreviousBlock) { + public BlockBuilder withHashPreviousBlock(String hashPreviousBlock) { this.hashPreviousBlock = hashPreviousBlock; return this; } - public BlockBuilder withHash(String hash) { + public BlockBuilder withHash(String hash) { this.hash = hash; return this; } - public BlockBuilder withTimestamp(Long timestamp) { + public BlockBuilder withTimestamp(Long timestamp) { this.timestamp = timestamp; return this; } - public BlockBuilder withMagicNumber(Integer magicNumber) { + public BlockBuilder withMagicNumber(Integer magicNumber) { this.magicNumber = magicNumber; return this; } - public BlockBuilder withData(String data) { - this.data = data; - return this; - } - - public Block build() { - return new Block(id, minerId, hashPreviousBlock, hash, timestamp, magicNumber, data); + public Block build() { + return new Block<>(id, minerId, hashPreviousBlock, hash, timestamp, magicNumber); } -} +} \ No newline at end of file diff --git a/src/blockchain/Blockchain.java b/src/blockchain/Blockchain.java index bf83119..c57e867 100644 --- a/src/blockchain/Blockchain.java +++ b/src/blockchain/Blockchain.java @@ -1,7 +1,11 @@ package blockchain; +import blockchain.crypto.SignValidator; +import blockchain.data.SignedData; import blockchain.data.format.DataFormatter; import blockchain.io.Persister; +import java.io.Serializable; +import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.LinkedList; @@ -9,45 +13,52 @@ import java.util.Objects; import java.util.StringJoiner; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; +import java.util.stream.Collectors; import java.util.stream.Stream; -public class Blockchain { +public class Blockchain { private static final Long ACCEPTABLE_TIME = TimeUnit.SECONDS.toMillis(5); private final ReentrantReadWriteLock readWritelock; - private final List blocks; + private final List> blocks; private final List pendingMessages; private final Persister persister; private int prefixLength; private DataFormatter dataFormatter; + private SignValidator signValidator; - public Blockchain(Persister persister, DataFormatter dataFormatter) { + private AtomicInteger idGenerator; + + public Blockchain(Persister persister, DataFormatter dataFormatter, + SignValidator signValidator) { this.readWritelock = new ReentrantReadWriteLock(); this.persister = persister; this.dataFormatter = dataFormatter; + this.signValidator = signValidator; this.prefixLength = 0; - List blocks = persister.load(); - if (!blocks.isEmpty() && validateAllChain(blocks)) { - this.blocks = blocks; + List> fileBlocks = persister.load(); + if (!fileBlocks.isEmpty() && validateAllChain(fileBlocks)) { + this.blocks = fileBlocks; } else { - new BlockBuilder().build(); this.blocks = new LinkedList<>(Collections.singleton( - new BlockBuilder() + new BlockBuilder() .withId(1) .withMinerId(0L) .withHashPreviousBlock("") .withHash("0") .withMagicNumber(0) .withTimestamp(new Date().getTime()).build())); - persister.save(blocks); + persister.save(fileBlocks); } pendingMessages = new LinkedList<>(); + this.idGenerator = new AtomicInteger(blocks.get(blocks.size() - 1).getId()); } public void accept(Block newBlock) { @@ -55,7 +66,7 @@ public void accept(Block newBlock) { try { writeLock.lock(); if (isValid(newBlock)) { - newBlock.setData(pendingData()); + newBlock.setData(new ArrayList<>(pendingMessages)); outputAndAdjust(newBlock); blocks.add(newBlock); pendingMessages.clear(); @@ -70,7 +81,9 @@ public void appendData(T data) { WriteLock writeLock = readWritelock.writeLock(); try { writeLock.lock(); - pendingMessages.add(data); + if (isSigned(dataFormatter.format(data), data.dataSignature())) { + pendingMessages.add(data); + } } finally { writeLock.unlock(); } @@ -115,6 +128,18 @@ public Block tail() { } } + public Integer nextId() { + return idGenerator.incrementAndGet(); + } + + private boolean isSigned(String data, byte[] dataSignature) { + try { + return true;//return signValidator.verifySignature(data.getBytes(), dataSignature); + } catch (Exception e) { + return false; + } + } + private boolean isValid(Block newBlock) { Block tailBlock = blocks.get(blocks.size() - 1); if (!newBlock.getHash().startsWith(getPrefix()) || !Objects @@ -129,14 +154,16 @@ private void outputAndAdjust(Block newBlock) { adjustComplexity(newBlock); } - private void outputStats(Block newBlock) { + private void outputStats(Block newBlock) { System.out.printf("Block:\n"); System.out.printf("Id: %s\n", newBlock.getId()); System.out.printf("Created by miner # %s\n", newBlock.getMinerId()); System.out.printf("Timestamp: %s\n", newBlock.getTimestamp()); System.out.printf("Magic number: %s\n", newBlock.getMagicNumber()); System.out.printf("Hash of the previous block:\n%s\n", newBlock.getHashPreviousBlock()); - System.out.printf("Block data: \n%s\n", newBlock.getData()); + System.out.printf("Block data: \n%s\n", newBlock.getData().stream().map( + x -> dataFormatter.format(x) + ).collect(Collectors.joining("\n"))); System.out.printf("Magic number: %s\n", newBlock.getMagicNumber()); System.out .printf("Block was generating for: %s seconds\n", getGenerationTime(newBlock) / 1000); @@ -158,15 +185,15 @@ private void adjustComplexity(Block newBlock) { } } - private Long getGenerationTime(Block newBlock) { + private Long getGenerationTime(Block newBlock) { return tail() == null ? 0 : (newBlock.getTimestamp() - tail().getTimestamp()); } - private boolean withinAcceptable(Block newBlock) { + private boolean withinAcceptable(Block newBlock) { return Math.abs(getGenerationTime(newBlock) - ACCEPTABLE_TIME) <= 1000; } - private boolean validateAllChain(List blocks) { + private boolean validateAllChain(List> blocks) { for (int i = 1; i < blocks.size(); i++) { Block prev = blocks.get(i - 1); Block cur = blocks.get(i); diff --git a/src/blockchain/Main.java b/src/blockchain/Main.java index 1493a79..9393c6e 100644 --- a/src/blockchain/Main.java +++ b/src/blockchain/Main.java @@ -3,49 +3,60 @@ import blockchain.crypto.Sign; import blockchain.crypto.SignValidator; import blockchain.data.Message; -import blockchain.data.format.PlanMessageFormat; +import blockchain.data.format.PlainMessageFormat; import blockchain.io.FilePersister; import blockchain.io.Persister; import blockchain.miners.Miner; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class Main { private static final String OUTPUT_FILE_NAME = "blockchain.ser"; private static final int NUMBER_OF_MINERS = 10; + private static AtomicLong ids = new AtomicLong(1); public static void main(String[] args) throws Exception { Sign sign = new Sign("keys/privateKey"); SignValidator signValidator = new SignValidator("keys/publicKey"); - Persister persister = new FilePersister(OUTPUT_FILE_NAME); + + Persister persister = new FilePersister<>(OUTPUT_FILE_NAME); Blockchain blockchain = new Blockchain<>(persister, - new PlanMessageFormat(), signValidator); + new PlainMessageFormat(), signValidator); - Thread[] miners = new Thread[NUMBER_OF_MINERS]; + List miners = Stream + .generate(() -> new Thread(new Miner(blockchain, ids.getAndIncrement()))) + .limit(NUMBER_OF_MINERS) + .collect(Collectors.toList()); - for (int i = 0; i < NUMBER_OF_MINERS; i++) { - miners[i] = new Thread(new Miner(blockchain, Long.valueOf(i), sign)); - } - for (Thread miner : miners) { - miner.start(); - } + miners.forEach(Thread::start); Thread.sleep(1000); - blockchain.appendData(new Message("Tom", "Hey, I'm first!")); - blockchain.appendData(new Message("Sarah", "It's not fair!")); + blockchain.appendData( + new Message(blockchain.nextId(), "Tom", "Hey, I'm first!", sign.sign("Hey, I'm first!"), + null)); + blockchain.appendData( + new Message(blockchain.nextId(), "Sarah", "It's not fair!", sign.sign("It's not fair!"), + null)); Thread.sleep(2000); blockchain.appendData( - new Message("Sarah", "You always will be first because it is your blockchain!")); + new Message(blockchain.nextId(), "Sarah", + "You always will be first because it is your blockchain!", + sign.sign("You always will be first because it is your blockchain!"), null)); Thread.sleep(1000); - blockchain.appendData(new Message("Sarah", "Anyway, thank you for this amazing chat.")); + blockchain.appendData( + new Message(blockchain.nextId(), "Sarah", "Anyway, thank you for this amazing chat.", + sign.sign("Anyway, thank you for this amazing chat."), null)); Thread.sleep(2000); + blockchain.appendData(new Message(blockchain.nextId(), "Tom", "You're welcome :)", + sign.sign("You're welcome :)"), null)); blockchain - .appendData(new Message("Tom", "You're welcome :)")); - blockchain.appendData( - new Message("Nick", "Hey Tom, nice chat")); + .appendData(new Message(blockchain.nextId(), "Nick", "Hey Tom, nice chat", + sign.sign("Hey Tom, nice chat"), null)); - for (Thread miner : miners) { - miner.interrupt(); - } + miners.forEach(Thread::interrupt); } } \ No newline at end of file diff --git a/src/blockchain/data/Message.java b/src/blockchain/data/Message.java index d1ecfeb..72d3f6f 100644 --- a/src/blockchain/data/Message.java +++ b/src/blockchain/data/Message.java @@ -1,13 +1,21 @@ package blockchain.data; -public class Message { +import java.io.Serializable; +public class Message implements SignedData, Serializable { + + private final Integer id; private final String author; private final String text; + private final byte[] dataSignature; + private final byte[] publicKey; - public Message(String author, String text) { + public Message(Integer id, String author, String text, byte[] dataSignature, byte[] publicKey) { + this.id = id; this.author = author; this.text = text; + this.dataSignature = dataSignature; + this.publicKey = publicKey; } public String getAuthor() { @@ -17,4 +25,19 @@ public String getAuthor() { public String getText() { return text; } + + @Override + public Integer id() { + return id; + } + + @Override + public byte[] dataSignature() { + return dataSignature; + } + + @Override + public byte[] publicKey() { + return publicKey; + } } diff --git a/src/blockchain/data/SignedData.java b/src/blockchain/data/SignedData.java new file mode 100644 index 0000000..69cc018 --- /dev/null +++ b/src/blockchain/data/SignedData.java @@ -0,0 +1,10 @@ +package blockchain.data; + +public interface SignedData { + + Integer id(); + + byte[] dataSignature(); + + byte[] publicKey(); +} diff --git a/src/blockchain/data/format/PlanMessageFormat.java b/src/blockchain/data/format/PlainMessageFormat.java similarity index 74% rename from src/blockchain/data/format/PlanMessageFormat.java rename to src/blockchain/data/format/PlainMessageFormat.java index 773b0fb..cfa7bf2 100644 --- a/src/blockchain/data/format/PlanMessageFormat.java +++ b/src/blockchain/data/format/PlainMessageFormat.java @@ -2,7 +2,7 @@ import blockchain.data.Message; -public class PlanMessageFormat implements DataFormatter { +public class PlainMessageFormat implements DataFormatter { @Override public String format(Message message) { diff --git a/src/blockchain/io/FilePersister.java b/src/blockchain/io/FilePersister.java index c996c9a..8535430 100644 --- a/src/blockchain/io/FilePersister.java +++ b/src/blockchain/io/FilePersister.java @@ -1,16 +1,18 @@ package blockchain.io; import blockchain.Block; +import blockchain.data.SignedData; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.io.Serializable; import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; -public class FilePersister implements Persister { +public class FilePersister implements Persister { private final String filename; @@ -20,7 +22,7 @@ public FilePersister(String filename) { } @Override - public void save(List blockchain) { + public void save(List> blockchain) { try (FileOutputStream fileOutputStream = new FileOutputStream(filename); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)) { objectOutputStream.writeObject(blockchain); @@ -30,11 +32,11 @@ public void save(List blockchain) { } @Override - public List load() { + public List> load() { if (fileExists()) { try (FileInputStream fileInputStream = new FileInputStream(filename); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)) { - return (List) objectInputStream.readObject(); + return (List>) objectInputStream.readObject(); } catch (IOException | ClassNotFoundException e) { return List.of(); } diff --git a/src/blockchain/io/Persister.java b/src/blockchain/io/Persister.java index 1004ad2..de31cce 100644 --- a/src/blockchain/io/Persister.java +++ b/src/blockchain/io/Persister.java @@ -1,12 +1,14 @@ package blockchain.io; import blockchain.Block; +import blockchain.data.SignedData; +import java.io.Serializable; import java.util.List; -public interface Persister { +public interface Persister { - void save(List blockchain); + void save(List> blockchain); - List load(); + List> load(); } diff --git a/src/blockchain/miners/Miner.java b/src/blockchain/miners/Miner.java index 589475f..52b83b2 100644 --- a/src/blockchain/miners/Miner.java +++ b/src/blockchain/miners/Miner.java @@ -1,50 +1,48 @@ package blockchain.miners; import blockchain.Block; +import blockchain.BlockBuilder; import blockchain.Blockchain; -import blockchain.crypto.Sign; import blockchain.utils.StringUtil; +import java.util.Date; import java.util.Random; public class Miner implements Runnable { private final Blockchain blockchain; private final Long id; - private Sign sign; private final Random random; - public Miner(Blockchain blockchain, Long id, Sign sign) { + public Miner(Blockchain blockchain, Long id) { this.blockchain = blockchain; this.id = id; - this.sign = sign; this.random = new Random(); } @Override public void run() { while (!Thread.currentThread().isInterrupted()) { - long timestamp = System.currentTimeMillis(); - Block prev; - String prefix; - String data; - int magicNumber; - String hash; - do { - prev = blockchain.tail(); - prefix = blockchain.getPrefix(); - data = blockchain.pendingData(); - magicNumber = random.nextInt(); - String dataUsedForHash = prev.getHash() + magicNumber + data; - hash = StringUtil.applySha256(dataUsedForHash); - } while (!hash.startsWith(prefix)); - Block newBlock = null; - try { - newBlock = new Block(prev.getId() + 1, id, prev.getHash(), hash, data, - sign.sign(data), timestamp, magicNumber, System.currentTimeMillis() - timestamp); - } catch (Exception e) { - Thread.currentThread().interrupt(); - } - blockchain.accept(newBlock); + Block prev = blockchain.tail(); + blockchain.accept(generateNextBlock(prev.getId() + 1, id, prev.getHash())); } } + + private Block generateNextBlock(int blockId, long minerId, String previousHash) { + BlockBuilder blockBuilder = new BlockBuilder() + .withId(blockId) + .withMinerId(minerId) + .withHashPreviousBlock(previousHash); + Integer magicNumber; + String hash; + String prefix; + do { + prefix = blockchain.getPrefix(); + magicNumber = random.nextInt(); + hash = StringUtil.applySha256(previousHash + magicNumber); + blockBuilder = blockBuilder.withHash(hash) + .withMagicNumber(magicNumber) + .withTimestamp(new Date().getTime()); + } while (!hash.startsWith(prefix)); + return blockBuilder.build(); + } } diff --git a/src/crypto/KeyPairGen.java b/src/crypto/KeyPairGen.java index 1bd266a..1c730ec 100644 --- a/src/crypto/KeyPairGen.java +++ b/src/crypto/KeyPairGen.java @@ -1,8 +1,10 @@ package crypto; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; @@ -11,8 +13,11 @@ public class KeyPairGen { - private KeyPairGenerator keyGen; - private KeyPair pair; + private static final String PUBLIC_KEY_PATH = "keys/publicKey"; + private static final String PRIVATE_KEY_PATH = "keys/privateKey"; + + private final KeyPairGenerator keyGen; + private PrivateKey privateKey; private PublicKey publicKey; @@ -22,10 +27,11 @@ private KeyPairGen(int keyLen) throws NoSuchAlgorithmException { } - private void genPair() { - pair = keyGen.generateKeyPair(); + private KeyPairGen genPair() { + KeyPair pair = keyGen.generateKeyPair(); privateKey = pair.getPrivate(); publicKey = pair.getPublic(); + return this; } private PrivateKey getPrivateKey() { @@ -37,20 +43,17 @@ private PublicKey getPublicKey() { } public static void main(String[] args) throws Exception { - KeyPairGen gen = new KeyPairGen(1024); - - gen.genPair(); - - writeToFile("keys/publicKey", gen.getPublicKey().getEncoded()); - writeToFile("keys/privateKey", gen.getPrivateKey().getEncoded()); + KeyPairGen gen = new KeyPairGen(1024) + .genPair(); + writeToFile(PUBLIC_KEY_PATH, gen.getPublicKey().getEncoded()); + writeToFile(PRIVATE_KEY_PATH, gen.getPrivateKey().getEncoded()); } private static void writeToFile(String path, byte[] key) throws IOException { - File f = new File(path); - f.getParentFile().mkdirs(); - FileOutputStream fos = new FileOutputStream(f); - fos.write(key); - fos.flush(); - fos.close(); + Path keyPath = Paths.get(path); + Files.createDirectories(keyPath.getParent()); + try (OutputStream fos = Files.newOutputStream(keyPath)) { + fos.write(key); + } } } From 4f2192b62de29057ea80d332e6c4acdf589ef297 Mon Sep 17 00:00:00 2001 From: oleksii Date: Thu, 3 Jan 2019 16:53:27 +0200 Subject: [PATCH 12/14] fourth phase implementation done code review --- src/blockchain/Block.java | 2 +- src/blockchain/BlockBuilder.java | 2 - src/blockchain/Blockchain.java | 85 ++++++++++++++---------- src/blockchain/Main.java | 37 ++++------- src/blockchain/crypto/Sign.java | 10 ++- src/blockchain/crypto/SignValidator.java | 7 +- src/blockchain/data/Message.java | 5 ++ src/blockchain/data/SignedData.java | 2 + src/blockchain/io/Messenger.java | 29 ++++++++ src/blockchain/miners/Miner.java | 20 ++++-- 10 files changed, 125 insertions(+), 74 deletions(-) create mode 100644 src/blockchain/io/Messenger.java diff --git a/src/blockchain/Block.java b/src/blockchain/Block.java index d28beff..8a4a22c 100644 --- a/src/blockchain/Block.java +++ b/src/blockchain/Block.java @@ -12,7 +12,7 @@ public class Block implements Serializable private final String hash; private final Long timestamp; private final Integer magicNumber; - private List data; + private List data = List.of(); public Block(Integer id, Long minerId, String hashPreviousBlock, String hash, diff --git a/src/blockchain/BlockBuilder.java b/src/blockchain/BlockBuilder.java index 0306842..d75fad7 100644 --- a/src/blockchain/BlockBuilder.java +++ b/src/blockchain/BlockBuilder.java @@ -11,8 +11,6 @@ public class BlockBuilder { private String hash; private Long timestamp; private Integer magicNumber; - private String data; - private byte[] dataSignature; public BlockBuilder withId(Integer id) { this.id = id; diff --git a/src/blockchain/Blockchain.java b/src/blockchain/Blockchain.java index c57e867..2f42a83 100644 --- a/src/blockchain/Blockchain.java +++ b/src/blockchain/Blockchain.java @@ -6,12 +6,14 @@ import blockchain.io.Persister; import java.io.Serializable; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Objects; -import java.util.StringJoiner; +import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -32,16 +34,13 @@ public class Blockchain { private int prefixLength; private DataFormatter dataFormatter; - private SignValidator signValidator; private AtomicInteger idGenerator; - public Blockchain(Persister persister, DataFormatter dataFormatter, - SignValidator signValidator) { + public Blockchain(Persister persister, DataFormatter dataFormatter) { this.readWritelock = new ReentrantReadWriteLock(); this.persister = persister; this.dataFormatter = dataFormatter; - this.signValidator = signValidator; this.prefixLength = 0; List> fileBlocks = persister.load(); if (!fileBlocks.isEmpty() && validateAllChain(fileBlocks)) { @@ -58,10 +57,10 @@ public Blockchain(Persister persister, DataFormatter dataFormatter, persister.save(fileBlocks); } pendingMessages = new LinkedList<>(); - this.idGenerator = new AtomicInteger(blocks.get(blocks.size() - 1).getId()); + this.idGenerator = new AtomicInteger(blocks.get(blocks.size() - 1).getId() + 1); } - public void accept(Block newBlock) { + public void accept(Block newBlock) { WriteLock writeLock = readWritelock.writeLock(); try { writeLock.lock(); @@ -77,16 +76,17 @@ public void accept(Block newBlock) { } } - public void appendData(T data) { + public boolean appendData(T data) { WriteLock writeLock = readWritelock.writeLock(); try { writeLock.lock(); - if (isSigned(dataFormatter.format(data), data.dataSignature())) { - pendingMessages.add(data); + if (isSigned(data) && dataIdentityValid(data)) { + return pendingMessages.add(data); } } finally { writeLock.unlock(); } + return false; } public String getPrefix() { @@ -101,24 +101,7 @@ public String getPrefix() { } } - public String pendingData() { - ReadLock readLock = readWritelock.readLock(); - try { - readLock.lock(); - StringJoiner stringJoiner = new StringJoiner("\n"); - if (pendingMessages.isEmpty()) { - return "no messages"; - } - pendingMessages.stream() - .map(dataFormatter::format) - .forEach(stringJoiner::add); - return stringJoiner.toString(); - } finally { - readLock.unlock(); - } - } - - public Block tail() { + public Block tail() { ReadLock readLock = readWritelock.readLock(); try { readLock.lock(); @@ -128,13 +111,14 @@ public Block tail() { } } - public Integer nextId() { + public Integer uniqueIdentity() { return idGenerator.incrementAndGet(); } - private boolean isSigned(String data, byte[] dataSignature) { + private boolean isSigned(T data) { try { - return true;//return signValidator.verifySignature(data.getBytes(), dataSignature); + SignValidator signValidator = new SignValidator(data.publicKey()); + return signValidator.verifySignature(data.raw(), data.dataSignature()); } catch (Exception e) { return false; } @@ -149,7 +133,7 @@ private boolean isValid(Block newBlock) { return true; } - private void outputAndAdjust(Block newBlock) { + private void outputAndAdjust(Block newBlock) { outputStats(newBlock); adjustComplexity(newBlock); } @@ -169,7 +153,7 @@ private void outputStats(Block newBlock) { .printf("Block was generating for: %s seconds\n", getGenerationTime(newBlock) / 1000); } - private void adjustComplexity(Block newBlock) { + private void adjustComplexity(Block newBlock) { if (!withinAcceptable(newBlock)) { if (ACCEPTABLE_TIME < getGenerationTime(newBlock)) { if (prefixLength > 0) { @@ -195,13 +179,42 @@ private boolean withinAcceptable(Block newBlock) { private boolean validateAllChain(List> blocks) { for (int i = 1; i < blocks.size(); i++) { - Block prev = blocks.get(i - 1); - Block cur = blocks.get(i); - if (!Objects.equals(prev.getHash(), cur.getHashPreviousBlock())) { + Block prev = blocks.get(i - 1); + Block cur = blocks.get(i); + Stream concatData = Stream.concat( + Objects.requireNonNullElse(prev.getData(), new ArrayList()).stream(), + Objects.requireNonNullElse(cur.getData(), new ArrayList()).stream()); + if (!Objects.equals(prev.getHash(), cur.getHashPreviousBlock()) + && dataIdentityValid(concatData.collect(Collectors.toList()))) { return false; } } return true; } + private boolean dataIdentityValid(List jointBlocks) { + for (int i = 1; i < jointBlocks.size(); i++) { + if (Objects.compare(jointBlocks.get(i - 1), jointBlocks.get(i), + Comparator.comparing(T::id)) > 0) { + return false; + } + } + return true; + } + + private boolean dataIdentityValid(T data) { + Optional lastEntry = getLastEntry(); + return lastEntry + .map(t -> t.id() < data.id()) + .orElse(true); + } + + private Optional getLastEntry() { + return blocks.stream() + .map(Block::getData) + .filter(List::isEmpty) + .flatMap(Collection::stream) + .limit(1) + .findFirst(); + } } diff --git a/src/blockchain/Main.java b/src/blockchain/Main.java index 9393c6e..224407b 100644 --- a/src/blockchain/Main.java +++ b/src/blockchain/Main.java @@ -1,10 +1,10 @@ package blockchain; import blockchain.crypto.Sign; -import blockchain.crypto.SignValidator; import blockchain.data.Message; import blockchain.data.format.PlainMessageFormat; import blockchain.io.FilePersister; +import blockchain.io.Messenger; import blockchain.io.Persister; import blockchain.miners.Miner; import java.util.List; @@ -16,46 +16,35 @@ public class Main { private static final String OUTPUT_FILE_NAME = "blockchain.ser"; private static final int NUMBER_OF_MINERS = 10; - private static AtomicLong ids = new AtomicLong(1); + private final static AtomicLong minersIdGenerator = new AtomicLong(1); public static void main(String[] args) throws Exception { - Sign sign = new Sign("keys/privateKey"); - SignValidator signValidator = new SignValidator("keys/publicKey"); + Sign sign = new Sign("keys/privateKey", "keys/publicKey"); Persister persister = new FilePersister<>(OUTPUT_FILE_NAME); Blockchain blockchain = new Blockchain<>(persister, - new PlainMessageFormat(), signValidator); + new PlainMessageFormat()); List miners = Stream - .generate(() -> new Thread(new Miner(blockchain, ids.getAndIncrement()))) + .generate(() -> new Thread(new Miner<>(blockchain, minersIdGenerator.getAndIncrement()))) .limit(NUMBER_OF_MINERS) .collect(Collectors.toList()); miners.forEach(Thread::start); + Messenger messenger = new Messenger(blockchain, sign); + Thread.sleep(1000); - blockchain.appendData( - new Message(blockchain.nextId(), "Tom", "Hey, I'm first!", sign.sign("Hey, I'm first!"), - null)); - blockchain.appendData( - new Message(blockchain.nextId(), "Sarah", "It's not fair!", sign.sign("It's not fair!"), - null)); + messenger.sendMessage("Tom", "Hey, I'm first!"); + messenger.sendMessage("Sarah", "It's not fair!"); Thread.sleep(2000); - blockchain.appendData( - new Message(blockchain.nextId(), "Sarah", - "You always will be first because it is your blockchain!", - sign.sign("You always will be first because it is your blockchain!"), null)); + messenger.sendMessage("Sarah", "You always will be first because it is your blockchain!"); Thread.sleep(1000); - blockchain.appendData( - new Message(blockchain.nextId(), "Sarah", "Anyway, thank you for this amazing chat.", - sign.sign("Anyway, thank you for this amazing chat."), null)); + messenger.sendMessage("Sarah", "Anyway, thank you for this amazing chat."); Thread.sleep(2000); - blockchain.appendData(new Message(blockchain.nextId(), "Tom", "You're welcome :)", - sign.sign("You're welcome :)"), null)); - blockchain - .appendData(new Message(blockchain.nextId(), "Nick", "Hey Tom, nice chat", - sign.sign("Hey Tom, nice chat"), null)); + messenger.sendMessage("Tom", "You're welcome :)"); + messenger.sendMessage("Nick", "Hey Tom, nice chat"); miners.forEach(Thread::interrupt); } diff --git a/src/blockchain/crypto/Sign.java b/src/blockchain/crypto/Sign.java index 0559bcf..e818899 100644 --- a/src/blockchain/crypto/Sign.java +++ b/src/blockchain/crypto/Sign.java @@ -9,10 +9,12 @@ public class Sign { - private PrivateKey privateKey; + private final byte[] publicKeyBytes; + private final PrivateKey privateKey; - public Sign(String privateKeyPath) throws Exception { + public Sign(String privateKeyPath, String publicKeyFilePath) throws Exception { byte[] keyBytes = Files.readAllBytes(new File(privateKeyPath).toPath()); + this.publicKeyBytes = Files.readAllBytes(new File(publicKeyFilePath).toPath()); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory kf = KeyFactory.getInstance("RSA"); this.privateKey = kf.generatePrivate(spec); @@ -24,4 +26,8 @@ public byte[] sign(String text) throws Exception { rsa.update(text.getBytes()); return rsa.sign(); } + + public byte[] getPublicKeyBytes() { + return publicKeyBytes; + } } diff --git a/src/blockchain/crypto/SignValidator.java b/src/blockchain/crypto/SignValidator.java index 8c17ffb..832036e 100644 --- a/src/blockchain/crypto/SignValidator.java +++ b/src/blockchain/crypto/SignValidator.java @@ -12,8 +12,11 @@ public class SignValidator { private PublicKey publicKey; public SignValidator(String publicKeyFilePath) throws Exception { - byte[] keyBytes = Files.readAllBytes(new File(publicKeyFilePath).toPath()); - X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes); + this(Files.readAllBytes(new File(publicKeyFilePath).toPath())); + } + + public SignValidator(byte[] publicKeyBytes) throws Exception { + X509EncodedKeySpec spec = new X509EncodedKeySpec(publicKeyBytes); KeyFactory kf = KeyFactory.getInstance("RSA"); publicKey = kf.generatePublic(spec); } diff --git a/src/blockchain/data/Message.java b/src/blockchain/data/Message.java index 72d3f6f..e82ecc5 100644 --- a/src/blockchain/data/Message.java +++ b/src/blockchain/data/Message.java @@ -31,6 +31,11 @@ public Integer id() { return id; } + @Override + public byte[] raw() { + return (getText() + id()).getBytes(); + } + @Override public byte[] dataSignature() { return dataSignature; diff --git a/src/blockchain/data/SignedData.java b/src/blockchain/data/SignedData.java index 69cc018..8297fd2 100644 --- a/src/blockchain/data/SignedData.java +++ b/src/blockchain/data/SignedData.java @@ -4,6 +4,8 @@ public interface SignedData { Integer id(); + byte [] raw(); + byte[] dataSignature(); byte[] publicKey(); diff --git a/src/blockchain/io/Messenger.java b/src/blockchain/io/Messenger.java new file mode 100644 index 0000000..3976f75 --- /dev/null +++ b/src/blockchain/io/Messenger.java @@ -0,0 +1,29 @@ +package blockchain.io; + +import blockchain.Blockchain; +import blockchain.crypto.Sign; +import blockchain.data.Message; + +public class Messenger { + + private Blockchain blockchain; + private Sign sign; + + public Messenger(Blockchain blockchain, Sign sign) { + this.blockchain = blockchain; + this.sign = sign; + } + + public boolean sendMessage(String user, String text) { + try { + Integer id = blockchain.uniqueIdentity(); + Message message = new Message(id, user, text, sign.sign(text + id), + sign.getPublicKeyBytes()); + return blockchain.appendData(message); + } catch (Exception e) { + e.printStackTrace(); + System.out.println("Message was not sent"); + } + return false; + } +} diff --git a/src/blockchain/miners/Miner.java b/src/blockchain/miners/Miner.java index 52b83b2..76e3e43 100644 --- a/src/blockchain/miners/Miner.java +++ b/src/blockchain/miners/Miner.java @@ -3,17 +3,19 @@ import blockchain.Block; import blockchain.BlockBuilder; import blockchain.Blockchain; +import blockchain.data.SignedData; import blockchain.utils.StringUtil; +import java.io.Serializable; import java.util.Date; import java.util.Random; -public class Miner implements Runnable { +public class Miner implements Runnable { - private final Blockchain blockchain; + private final Blockchain blockchain; private final Long id; private final Random random; - public Miner(Blockchain blockchain, Long id) { + public Miner(Blockchain blockchain, Long id) { this.blockchain = blockchain; this.id = id; this.random = new Random(); @@ -22,16 +24,19 @@ public Miner(Blockchain blockchain, Long id) { @Override public void run() { while (!Thread.currentThread().isInterrupted()) { - Block prev = blockchain.tail(); - blockchain.accept(generateNextBlock(prev.getId() + 1, id, prev.getHash())); + Block prev = blockchain.tail(); + blockchain.accept( + generateNextBlock(prev.getId() + 1, id, prev.getHash()) + ); } } - private Block generateNextBlock(int blockId, long minerId, String previousHash) { - BlockBuilder blockBuilder = new BlockBuilder() + private Block generateNextBlock(int blockId, long minerId, String previousHash) { + BlockBuilder blockBuilder = new BlockBuilder() .withId(blockId) .withMinerId(minerId) .withHashPreviousBlock(previousHash); + Integer magicNumber; String hash; String prefix; @@ -43,6 +48,7 @@ private Block generateNextBlock(int blockId, long minerId, String previousHash) .withMagicNumber(magicNumber) .withTimestamp(new Date().getTime()); } while (!hash.startsWith(prefix)); + return blockBuilder.build(); } } From 5457aab2e40a9d371cdd2d7ce7fbe375ca73f793 Mon Sep 17 00:00:00 2001 From: oleksii Date: Thu, 10 Jan 2019 23:53:49 +0200 Subject: [PATCH 13/14] final phase implementation done --- src/blockchain/Blockchain.java | 27 ++++++++- src/blockchain/Main.java | 50 +++++++++++------ src/blockchain/data/Transaction.java | 55 +++++++++++++++++++ .../data/format/TransactionPrintFormat.java | 13 +++++ src/blockchain/io/Wallet.java | 49 +++++++++++++++++ 5 files changed, 177 insertions(+), 17 deletions(-) create mode 100644 src/blockchain/data/Transaction.java create mode 100644 src/blockchain/data/format/TransactionPrintFormat.java create mode 100644 src/blockchain/io/Wallet.java diff --git a/src/blockchain/Blockchain.java b/src/blockchain/Blockchain.java index 2f42a83..2077f17 100644 --- a/src/blockchain/Blockchain.java +++ b/src/blockchain/Blockchain.java @@ -19,6 +19,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; +import java.util.function.BiFunction; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -36,8 +37,11 @@ public class Blockchain { private DataFormatter dataFormatter; private AtomicInteger idGenerator; + private BiFunction> systemFeedback; - public Blockchain(Persister persister, DataFormatter dataFormatter) { + public Blockchain(Persister persister, DataFormatter dataFormatter, + BiFunction> systemFeedback) { + this.systemFeedback = systemFeedback; this.readWritelock = new ReentrantReadWriteLock(); this.persister = persister; this.dataFormatter = dataFormatter; @@ -65,6 +69,7 @@ public void accept(Block newBlock) { try { writeLock.lock(); if (isValid(newBlock)) { + applyFeedback(String.format("miner%d", newBlock.getMinerId())); newBlock.setData(new ArrayList<>(pendingMessages)); outputAndAdjust(newBlock); blocks.add(newBlock); @@ -115,6 +120,19 @@ public Integer uniqueIdentity() { return idGenerator.incrementAndGet(); } + public Stream data() { + ReadLock readLock = readWritelock.readLock(); + try { + readLock.lock(); + return blocks.stream() + .map(Block::getData) + .filter(List::isEmpty) + .flatMap(Collection::stream); + } finally { + readLock.unlock(); + } + } + private boolean isSigned(T data) { try { SignValidator signValidator = new SignValidator(data.publicKey()); @@ -217,4 +235,11 @@ private Optional getLastEntry() { .limit(1) .findFirst(); } + + private void applyFeedback(String miner) { + Optional feedback = systemFeedback.apply(uniqueIdentity(), miner); + feedback.ifPresent( + this::appendData + ); + } } diff --git a/src/blockchain/Main.java b/src/blockchain/Main.java index 224407b..c69ae71 100644 --- a/src/blockchain/Main.java +++ b/src/blockchain/Main.java @@ -1,13 +1,14 @@ package blockchain; import blockchain.crypto.Sign; -import blockchain.data.Message; -import blockchain.data.format.PlainMessageFormat; +import blockchain.data.Transaction; +import blockchain.data.format.TransactionPrintFormat; import blockchain.io.FilePersister; -import blockchain.io.Messenger; import blockchain.io.Persister; +import blockchain.io.Wallet; import blockchain.miners.Miner; import java.util.List; +import java.util.Optional; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -22,9 +23,18 @@ public static void main(String[] args) throws Exception { Sign sign = new Sign("keys/privateKey", "keys/publicKey"); - Persister persister = new FilePersister<>(OUTPUT_FILE_NAME); - Blockchain blockchain = new Blockchain<>(persister, - new PlainMessageFormat()); + Persister persister = new FilePersister<>(OUTPUT_FILE_NAME); + Blockchain blockchain = new Blockchain<>(persister, new TransactionPrintFormat(), + (id, x) -> { + try { + return Optional.of(new + Transaction(id, "System", x, 100.0, + sign.sign(String.format("%s,%s,%s", "System", x, 100.0) + id), + sign.getPublicKeyBytes())); + } catch (Exception e) { + return Optional.empty(); + } + }); List miners = Stream .generate(() -> new Thread(new Miner<>(blockchain, minersIdGenerator.getAndIncrement()))) @@ -33,18 +43,26 @@ public static void main(String[] args) throws Exception { miners.forEach(Thread::start); - Messenger messenger = new Messenger(blockchain, sign); + Wallet wallet = new Wallet(blockchain, sign); - Thread.sleep(1000); - messenger.sendMessage("Tom", "Hey, I'm first!"); - messenger.sendMessage("Sarah", "It's not fair!"); + wallet.sendMoney("miner9", "miner1", 30); + wallet.sendMoney("miner9", "miner2", 30); + wallet.sendMoney("miner9", "Nick", 30); Thread.sleep(2000); - messenger.sendMessage("Sarah", "You always will be first because it is your blockchain!"); - Thread.sleep(1000); - messenger.sendMessage("Sarah", "Anyway, thank you for this amazing chat."); - Thread.sleep(2000); - messenger.sendMessage("Tom", "You're welcome :)"); - messenger.sendMessage("Nick", "Hey Tom, nice chat"); + wallet.sendMoney("miner9", "Bob", 10); + wallet.sendMoney("miner7", "Alice", 10); + wallet.sendMoney("Nick", "ShoesShop", 1); + wallet.sendMoney("Nick", "FastFood", 2); + wallet.sendMoney("Nick", "CarShop", 15); + wallet.sendMoney("miner7", "CarShop", 90); + Thread.sleep(3000); + wallet.sendMoney("CarShop", "Worker1", 10); + wallet.sendMoney("CarShop", "Worker2", 10); + wallet.sendMoney("CarShop", "Worker3", 10); + wallet.sendMoney("CarShop", "Director1", 30); + wallet.sendMoney("CarShop", "CarPartsShop", 45); + wallet.sendMoney("Bob", "GamingShop", 5); + wallet.sendMoney("Alice", "BeautyShop", 5); miners.forEach(Thread::interrupt); } diff --git a/src/blockchain/data/Transaction.java b/src/blockchain/data/Transaction.java new file mode 100644 index 0000000..3573430 --- /dev/null +++ b/src/blockchain/data/Transaction.java @@ -0,0 +1,55 @@ +package blockchain.data; + +import java.io.Serializable; + +public class Transaction implements SignedData, Serializable { + + private Integer id; + private String from; + private String to; + private Double amount; + private byte[] dataSignature; + private byte[] publicKey; + + public Transaction(Integer id, String from, String to, Double amount, byte[] dataSignature, + byte[] publicKey) { + this.id = id; + this.from = from; + this.to = to; + this.amount = amount; + this.dataSignature = dataSignature; + this.publicKey = publicKey; + } + + @Override + public Integer id() { + return id; + } + + @Override + public byte[] raw() { + return (String.format("%s,%s,%s", from, to, amount) + id()).getBytes(); + } + + @Override + public byte[] dataSignature() { + return dataSignature; + } + + @Override + public byte[] publicKey() { + return publicKey; + } + + public String getFrom() { + return from; + } + + public String getTo() { + return to; + } + + public Double getAmount() { + return amount; + } +} diff --git a/src/blockchain/data/format/TransactionPrintFormat.java b/src/blockchain/data/format/TransactionPrintFormat.java new file mode 100644 index 0000000..10537fe --- /dev/null +++ b/src/blockchain/data/format/TransactionPrintFormat.java @@ -0,0 +1,13 @@ +package blockchain.data.format; + +import blockchain.data.Transaction; + +public class TransactionPrintFormat implements DataFormatter { + + @Override + public String format(Transaction transaction) { + return String + .format("%s sent %s VC to %s", transaction.getFrom(), transaction.getAmount(), + transaction.getTo()); + } +} diff --git a/src/blockchain/io/Wallet.java b/src/blockchain/io/Wallet.java new file mode 100644 index 0000000..a120533 --- /dev/null +++ b/src/blockchain/io/Wallet.java @@ -0,0 +1,49 @@ +package blockchain.io; + +import blockchain.Blockchain; +import blockchain.crypto.Sign; +import blockchain.data.Transaction; +import java.util.Objects; + +public class Wallet { + + private static final double INIT_SUM = 100.0; + + private Blockchain blockchain; + private Sign sign; + + public Wallet(Blockchain blockchain, Sign sign) { + this.blockchain = blockchain; + this.sign = sign; + } + + public boolean sendMoney(String from, String to, double amount) { + try { + if (!isEnoughMoney(from, amount)) { + return false; + } + Integer id = blockchain.uniqueIdentity(); + Transaction message = new Transaction(id, from, to, amount, + sign.sign(String.format("%s,%s,%s", + from, to, amount) + id), + sign.getPublicKeyBytes()); + return blockchain.appendData(message); + } catch (Exception e) { + e.printStackTrace(); + System.out.println("Message was not sent"); + } + return false; + } + + private boolean isEnoughMoney(String from, double amount) { + double sentSum = -blockchain.data().filter( + d -> Objects.equals(d.getFrom(), from)) + .mapToDouble(Transaction::getAmount) + .sum(); + double recvSum = blockchain.data().filter( + d -> Objects.equals(d.getTo(), from)) + .mapToDouble(Transaction::getAmount) + .sum(); + return INIT_SUM + sentSum + recvSum >= amount; + } +} From 84a8a56e9c8f7dc23bbbb57ba9036a3002f8fb44 Mon Sep 17 00:00:00 2001 From: oleksii Date: Sat, 26 Jan 2019 16:13:14 +0200 Subject: [PATCH 14/14] final phase implementation done fix code review comments --- src/blockchain/Blockchain.java | 10 ++-------- src/blockchain/Main.java | 12 +----------- src/blockchain/PayForMining.java | 27 +++++++++++++++++++++++++++ src/blockchain/SystemFeedback.java | 8 ++++++++ src/blockchain/io/FilePersister.java | 1 - 5 files changed, 38 insertions(+), 20 deletions(-) create mode 100644 src/blockchain/PayForMining.java create mode 100644 src/blockchain/SystemFeedback.java diff --git a/src/blockchain/Blockchain.java b/src/blockchain/Blockchain.java index 2077f17..ea685ce 100644 --- a/src/blockchain/Blockchain.java +++ b/src/blockchain/Blockchain.java @@ -40,7 +40,7 @@ public class Blockchain { private BiFunction> systemFeedback; public Blockchain(Persister persister, DataFormatter dataFormatter, - BiFunction> systemFeedback) { + SystemFeedback systemFeedback) { this.systemFeedback = systemFeedback; this.readWritelock = new ReentrantReadWriteLock(); this.persister = persister; @@ -121,16 +121,10 @@ public Integer uniqueIdentity() { } public Stream data() { - ReadLock readLock = readWritelock.readLock(); - try { - readLock.lock(); - return blocks.stream() + return new ArrayList<>(blocks).stream() .map(Block::getData) .filter(List::isEmpty) .flatMap(Collection::stream); - } finally { - readLock.unlock(); - } } private boolean isSigned(T data) { diff --git a/src/blockchain/Main.java b/src/blockchain/Main.java index c69ae71..22dc653 100644 --- a/src/blockchain/Main.java +++ b/src/blockchain/Main.java @@ -8,7 +8,6 @@ import blockchain.io.Wallet; import blockchain.miners.Miner; import java.util.List; -import java.util.Optional; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -25,16 +24,7 @@ public static void main(String[] args) throws Exception { Persister persister = new FilePersister<>(OUTPUT_FILE_NAME); Blockchain blockchain = new Blockchain<>(persister, new TransactionPrintFormat(), - (id, x) -> { - try { - return Optional.of(new - Transaction(id, "System", x, 100.0, - sign.sign(String.format("%s,%s,%s", "System", x, 100.0) + id), - sign.getPublicKeyBytes())); - } catch (Exception e) { - return Optional.empty(); - } - }); + new PayForMining(sign)); List miners = Stream .generate(() -> new Thread(new Miner<>(blockchain, minersIdGenerator.getAndIncrement()))) diff --git a/src/blockchain/PayForMining.java b/src/blockchain/PayForMining.java new file mode 100644 index 0000000..e27d50d --- /dev/null +++ b/src/blockchain/PayForMining.java @@ -0,0 +1,27 @@ +package blockchain; + +import blockchain.crypto.Sign; +import blockchain.data.Transaction; +import java.util.Optional; + +public class PayForMining implements SystemFeedback { + + private Sign sign; + + public PayForMining(Sign sign) { + + this.sign = sign; + } + + @Override + public Optional apply(Integer id, String x) { + try { + return Optional.of(new + Transaction(id, "System", x, 100.0, + sign.sign(String.format("%s,%s,%s", "System", x, 100.0) + id), + sign.getPublicKeyBytes())); + } catch (Exception e) { + return Optional.empty(); + } + } +} diff --git a/src/blockchain/SystemFeedback.java b/src/blockchain/SystemFeedback.java new file mode 100644 index 0000000..11e9375 --- /dev/null +++ b/src/blockchain/SystemFeedback.java @@ -0,0 +1,8 @@ +package blockchain; + +import java.util.Optional; +import java.util.function.BiFunction; + +public interface SystemFeedback extends BiFunction> { + +} diff --git a/src/blockchain/io/FilePersister.java b/src/blockchain/io/FilePersister.java index 8535430..451f60b 100644 --- a/src/blockchain/io/FilePersister.java +++ b/src/blockchain/io/FilePersister.java @@ -14,7 +14,6 @@ public class FilePersister implements Persister { - private final String filename; public FilePersister(String filename) {