Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions src/blockchain/Block.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package blockchain;

import java.io.Serializable;

public class Block implements Serializable {

private final Integer id;
private final Long minerId;
private final String hashPreviousBlock;
private final String hash;
private final Long timestamp;
private final Integer magicNumber;


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.timestamp = timestamp;
this.magicNumber = magicNumber;
}

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 getMinerId() {
return minerId;
}
}
110 changes: 110 additions & 0 deletions src/blockchain/Blockchain.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package blockchain;

import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;

public class Blockchain {

private static final Long ACCEPTABLE_TIME = TimeUnit.SECONDS.toMillis(5);

private final List<Block> blocks;
private int prefixLength;
private final Persister persister;

public Blockchain(Persister persister) {
this.persister = persister;
this.prefixLength = 0;
List<Block> blocks = persister.load();
if (!blocks.isEmpty() && validateAllChain(blocks)) {
this.blocks = blocks;
} else {
this.blocks = new LinkedList<>(Collections.singleton(
new Block(1, 0L, "", "0", new Date().getTime(), 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);
}

private boolean isValid(Block newBlock) {
Block tailBlock = blocks.get(blocks.size() - 1);
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", getGenerationTime(newBlock) / 1000);
}

private void adjustComplexity(Block newBlock) {
if (!withinAcceptable(newBlock)) {
if (ACCEPTABLE_TIME < getGenerationTime(newBlock)) {
if (prefixLength > 0) {
prefixLength--;
}
System.out.printf("N was decreased to %s\n\n", prefixLength);
} else {
prefixLength++;
System.out.printf("N was increased to %s\n\n", prefixLength);
}
} else {
System.out.printf("N stays the same\n\n");
}
}

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<Block> 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;
}

}
47 changes: 47 additions & 0 deletions src/blockchain/FilePersister.java
Original file line number Diff line number Diff line change
@@ -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<Block> blockchain) {
try (FileOutputStream fileOutputStream = new FileOutputStream(filename);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)) {
objectOutputStream.writeObject(blockchain);
} catch (IOException e) {
throw new RuntimeException();
}
}

@Override
public List<Block> load() {
if (fileExists()) {
try (FileInputStream fileInputStream = new FileInputStream(filename);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)) {
return (List<Block>) objectInputStream.readObject();
} catch (IOException | ClassNotFoundException e) {
return List.of();
}
}
return List.of();
}

private boolean fileExists() {
return Files.exists(Paths.get(filename));
}
}
23 changes: 21 additions & 2 deletions src/blockchain/Main.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,26 @@
package blockchain;

import blockchain.miners.Miner;

public class Main {
public static void main(String[] args) {
System.out.print("Hello world!");

public static void main(String[] args) throws InterruptedException {

int NUMBER_OF_MINERS = 10;

Persister persister = new FilePersister("blockchain.ser");
Blockchain blockchain = new Blockchain(persister);

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)));
}
for (Thread miner : miners) {
miner.start();
}
Thread.sleep(10000);
for (Thread miner : miners) {
miner.interrupt();
}
}
}
11 changes: 11 additions & 0 deletions src/blockchain/Persister.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package blockchain;

import java.util.List;

public interface Persister {

void save(List<Block> blockchain);

List<Block> load();
}

28 changes: 28 additions & 0 deletions src/blockchain/StringUtil.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
39 changes: 39 additions & 0 deletions src/blockchain/miners/Miner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package blockchain.miners;

import blockchain.Block;
import blockchain.Blockchain;
import blockchain.StringUtil;
import java.util.Random;

public class Miner implements Runnable {

private final Blockchain blockchain;
private final Random random;
private final Long id;

public Miner(Blockchain blockchain, Long id) {
this.blockchain = blockchain;
this.id = id;
this.random = new Random();
}

@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
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);
}
}