diff --git a/src/blockchain/Block.java b/src/blockchain/Block.java new file mode 100644 index 0000000..3721027 --- /dev/null +++ b/src/blockchain/Block.java @@ -0,0 +1,48 @@ +package blockchain; + +import java.io.Serializable; + +public class Block implements Serializable { + + private final Integer id; + private final String hashPreviousBlock; + private final String hash; + private final Long timestamp; + private final Integer magicNumber; + private final Long generationTime; + + + public Block(Integer id, String hashPreviousBlock, String hash, + Long timestamp, Integer magicNumber, Long generationTime) { + this.id = id; + this.hashPreviousBlock = hashPreviousBlock; + this.hash = hash; + this.timestamp = timestamp; + this.magicNumber = magicNumber; + this.generationTime = generationTime; + } + + public Integer getId() { + return id; + } + + public String getHashPreviousBlock() { + return hashPreviousBlock; + } + + public String getHash() { + return hash; + } + + public Long getTimestamp() { + return timestamp; + } + + public Integer getMagicNumber() { + return magicNumber; + } + + public Long getGenerationTime() { + return generationTime; + } +} diff --git a/src/blockchain/Blockchain.java b/src/blockchain/Blockchain.java new file mode 100644 index 0000000..a372326 --- /dev/null +++ b/src/blockchain/Blockchain.java @@ -0,0 +1,73 @@ +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.stream.Stream; + +public class Blockchain implements Iterable { + + private final List blocks; + private final Random random; + private final String prefix; + private final Persister persister; + + 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 { + this.blocks = new LinkedList<>(Collections.singleton( + generateNextBlock(1, "0"))); + persister.save(blocks); + } + } + + + public void generate() { + Block tailBlock = blocks.get(blocks.size() - 1); + Block newBlock = generateNextBlock(tailBlock.getId() + 1, tailBlock.getHash()); + 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); + if (!cur.getHashPreviousBlock().equals(prev.getHash())) { + return false; + } + } + return true; + } + + private Block generateNextBlock(int id, String hashPreviousBlock) { + 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, 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 adf1d02..e7db2ea 100644 --- a/src/blockchain/Main.java +++ b/src/blockchain/Main.java @@ -1,7 +1,30 @@ package blockchain; +import java.util.Scanner; + public class Main { - public static void main(String[] args) { - System.out.print("Hello world!"); + + 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: "); + + Persister persister = new FilePersister("blockchain.ser"); + Blockchain blockchain = new Blockchain(scanner.nextInt(), persister); + + 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("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); + } } + } } \ No newline at end of file diff --git a/src/blockchain/Persister.java b/src/blockchain/Persister.java new file mode 100644 index 0000000..fe58027 --- /dev/null +++ b/src/blockchain/Persister.java @@ -0,0 +1,11 @@ +package blockchain; + +import java.util.List; + +public interface Persister { + + void save(List blockchain); + + List load(); +} + 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); + } + } +}