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
58 changes: 58 additions & 0 deletions src/blockchain/Block.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package blockchain;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code looks good and readable. Convenient separation into classes and methods.


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;
private String data;


public Block(Integer id, Long minerId, String hashPreviousBlock, String hash, Long timestamp,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the constructor takes a lot of parameters. You may try to use the Builder pattern or combine some parameters together in a single entity

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() {
return id;
}

public String getHashPreviousBlock() {
return hashPreviousBlock;
}

public String getHash() {
return hash;
}

public Long getTimestamp() {
return timestamp;
}

public String getData() {
return data;
}

public void setData(String data) {
this.data = data;
}

public Integer getMagicNumber() {
return magicNumber;
}

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

import blockchain.data.format.DataFormatter;
import blockchain.io.Persister;
import java.util.Collections;
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<T> {

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

private final ReentrantReadWriteLock readWritelock;

private final List<Block> blocks;
private final List<T> pendingMessages;
private final Persister persister;

private int prefixLength;
private DataFormatter<T> dataFormatter;

public Blockchain(Persister persister, DataFormatter<T> dataFormatter) {
this.readWritelock = new ReentrantReadWriteLock();
this.persister = persister;
this.dataFormatter = dataFormatter;
this.prefixLength = 0;
List<Block> blocks = persister.load();
if (!blocks.isEmpty() && validateAllChain(blocks)) {
this.blocks = blocks;
} else {
new BlockBuilder().build();
this.blocks = new LinkedList<>(Collections.singleton(
new BlockBuilder()
.withId(1)
.withMinerId(0L)
.withHashPreviousBlock("")
.withHash("0")
.withMagicNumber(0)
.withTimestamp(new Date().getTime()).build()));
persister.save(blocks);
}
pendingMessages = new LinkedList<>();
}

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 void appendData(T data) {
WriteLock writeLock = readWritelock.writeLock();
try {
writeLock.lock();
pendingMessages.add(data);
} finally {
writeLock.unlock();
}
}

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 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() {
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()) || !Objects
.equals(newBlock.getHashPreviousBlock(), 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("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", getGenerationTime(newBlock) / 1000);
}

private void adjustComplexity(Block newBlock) {
if (!withinAcceptable(newBlock)) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is good idea to encapsulate the condition's logic in a separated method

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 (!Objects.equals(prev.getHash(), cur.getHashPreviousBlock())) {
return false;
}
}
return true;
}

}
45 changes: 42 additions & 3 deletions src/blockchain/Main.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,46 @@
package blockchain;

import blockchain.data.Message;
import blockchain.data.format.PlanMessageFormat;
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 {
public static void main(String[] args) {
System.out.print("Hello world!");
}

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 {

Persister persister = new FilePersister(OUTPUT_FILE_NAME);
Blockchain<Message> blockchain = new Blockchain<>(persister,
new PlanMessageFormat());

List<Thread> miners = Stream
.generate(() -> new Thread(new Miner(blockchain, ids.getAndIncrement())))
.limit(NUMBER_OF_MINERS)
.collect(Collectors.toList());

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!"));
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"));

miners.forEach(Thread::interrupt);
}
}
20 changes: 20 additions & 0 deletions src/blockchain/data/Message.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package blockchain.data;

public class Message {

private final String author;
private final String text;

public Message(String author, String text) {
this.author = author;
this.text = text;
}

public String getAuthor() {
return author;
}

public String getText() {
return text;
}
}
6 changes: 6 additions & 0 deletions src/blockchain/data/format/DataFormatter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package blockchain.data.format;

public interface DataFormatter<T> {

String format(T message);
}
11 changes: 11 additions & 0 deletions src/blockchain/data/format/PlanMessageFormat.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package blockchain.data.format;

import blockchain.data.Message;

public class PlanMessageFormat implements DataFormatter<Message> {

@Override
public String format(Message message) {
return String.format("%s: %s", message.getAuthor(), message.getText());
}
}
Loading