-
Notifications
You must be signed in to change notification settings - Fork 6
Part five #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Part five #9
Changes from all commits
fe8e821
ddac0ff
7078dff
ee39a82
849cbc3
7daf197
3ae01f1
7bc0f21
bcc9223
ae3b3ab
de514b1
4a2da61
4f2192b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| package blockchain; | ||
|
|
||
| import blockchain.data.SignedData; | ||
| import java.io.Serializable; | ||
| import java.util.List; | ||
|
|
||
| public class Block<T extends SignedData & Serializable> 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 List<T> data = List.of(); | ||
|
|
||
|
|
||
| public Block(Integer id, Long minerId, String hashPreviousBlock, String hash, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Personally, I don’t really like constructors with a lot of arguments (3-4+) of the same (String, String) or almost the same types (Integer, Long). But you use builder below which can be added inside this class to hide the constructor. |
||
| 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; | ||
| } | ||
|
|
||
| public List<T> getData() { | ||
| return data; | ||
| } | ||
|
|
||
| public void setData(List<T> data) { | ||
| this.data = data; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| package blockchain; | ||
|
|
||
| import blockchain.data.SignedData; | ||
| import java.io.Serializable; | ||
|
|
||
| public class BlockBuilder<T extends SignedData & Serializable> { | ||
|
|
||
| private Integer id; | ||
| private Long minerId; | ||
| private String hashPreviousBlock; | ||
| private String hash; | ||
| private Long timestamp; | ||
| private Integer magicNumber; | ||
|
|
||
| public BlockBuilder<T> withId(Integer id) { | ||
| this.id = id; | ||
| return this; | ||
| } | ||
|
|
||
| public BlockBuilder<T> withMinerId(Long minerId) { | ||
| this.minerId = minerId; | ||
| return this; | ||
| } | ||
|
|
||
| public BlockBuilder<T> withHashPreviousBlock(String hashPreviousBlock) { | ||
| this.hashPreviousBlock = hashPreviousBlock; | ||
| return this; | ||
| } | ||
|
|
||
| public BlockBuilder<T> withHash(String hash) { | ||
| this.hash = hash; | ||
| return this; | ||
| } | ||
|
|
||
| public BlockBuilder<T> withTimestamp(Long timestamp) { | ||
| this.timestamp = timestamp; | ||
| return this; | ||
| } | ||
|
|
||
| public BlockBuilder<T> withMagicNumber(Integer magicNumber) { | ||
| this.magicNumber = magicNumber; | ||
| return this; | ||
| } | ||
|
|
||
| public Block<T> build() { | ||
| return new Block<>(id, minerId, hashPreviousBlock, hash, timestamp, magicNumber); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,220 @@ | ||
| 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.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.Optional; | ||
| 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<T extends SignedData & Serializable> { | ||
|
|
||
| private static final Long ACCEPTABLE_TIME = TimeUnit.SECONDS.toMillis(5); | ||
|
|
||
| private final ReentrantReadWriteLock readWritelock; | ||
|
|
||
| private final List<Block<T>> blocks; | ||
| private final List<T> pendingMessages; | ||
| private final Persister persister; | ||
|
|
||
| private int prefixLength; | ||
| private DataFormatter<T> dataFormatter; | ||
|
|
||
| private AtomicInteger idGenerator; | ||
|
|
||
| public Blockchain(Persister<T> persister, DataFormatter<T> dataFormatter) { | ||
| this.readWritelock = new ReentrantReadWriteLock(); | ||
| this.persister = persister; | ||
| this.dataFormatter = dataFormatter; | ||
| this.prefixLength = 0; | ||
| List<Block<T>> fileBlocks = persister.load(); | ||
| if (!fileBlocks.isEmpty() && validateAllChain(fileBlocks)) { | ||
| this.blocks = fileBlocks; | ||
| } else { | ||
| this.blocks = new LinkedList<>(Collections.singleton( | ||
| new BlockBuilder<T>() | ||
| .withId(1) | ||
| .withMinerId(0L) | ||
| .withHashPreviousBlock("") | ||
| .withHash("0") | ||
| .withMagicNumber(0) | ||
| .withTimestamp(new Date().getTime()).build())); | ||
| persister.save(fileBlocks); | ||
| } | ||
| pendingMessages = new LinkedList<>(); | ||
| this.idGenerator = new AtomicInteger(blocks.get(blocks.size() - 1).getId() + 1); | ||
| } | ||
|
|
||
| public void accept(Block<T> newBlock) { | ||
| WriteLock writeLock = readWritelock.writeLock(); | ||
| try { | ||
| writeLock.lock(); | ||
| if (isValid(newBlock)) { | ||
| newBlock.setData(new ArrayList<>(pendingMessages)); | ||
| outputAndAdjust(newBlock); | ||
| blocks.add(newBlock); | ||
| pendingMessages.clear(); | ||
| persister.save(blocks); | ||
| } | ||
| } finally { | ||
| writeLock.unlock(); | ||
| } | ||
| } | ||
|
|
||
| public boolean appendData(T data) { | ||
| WriteLock writeLock = readWritelock.writeLock(); | ||
| try { | ||
| writeLock.lock(); | ||
| if (isSigned(data) && dataIdentityValid(data)) { | ||
| return pendingMessages.add(data); | ||
| } | ||
| } finally { | ||
| writeLock.unlock(); | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| public String getPrefix() { | ||
| ReadLock readLock = readWritelock.readLock(); | ||
| try { | ||
| readLock.lock(); | ||
| return Stream.iterate("0", x -> "0") | ||
| .limit(prefixLength) | ||
| .reduce("", (x, y) -> x + y); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a reduce-like function joining for strings. You may try to use it. |
||
| } finally { | ||
| readLock.unlock(); | ||
| } | ||
| } | ||
|
|
||
| public Block<T> tail() { | ||
| ReadLock readLock = readWritelock.readLock(); | ||
| try { | ||
| readLock.lock(); | ||
| return blocks.get(blocks.size() - 1); | ||
| } finally { | ||
| readLock.unlock(); | ||
| } | ||
| } | ||
|
|
||
| public Integer uniqueIdentity() { | ||
| return idGenerator.incrementAndGet(); | ||
| } | ||
|
|
||
| private boolean isSigned(T data) { | ||
| try { | ||
| SignValidator signValidator = new SignValidator(data.publicKey()); | ||
| return signValidator.verifySignature(data.raw(), data.dataSignature()); | ||
| } catch (Exception e) { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| 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<T> newBlock) { | ||
| outputStats(newBlock); | ||
| adjustComplexity(newBlock); | ||
| } | ||
|
|
||
| private void outputStats(Block<T> 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().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); | ||
| } | ||
|
|
||
| private void adjustComplexity(Block<T> 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<T>> blocks) { | ||
| for (int i = 1; i < blocks.size(); i++) { | ||
| Block<T> prev = blocks.get(i - 1); | ||
| Block<T> cur = blocks.get(i); | ||
| Stream<T> concatData = Stream.concat( | ||
| Objects.requireNonNullElse(prev.getData(), new ArrayList<T>()).stream(), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
| Objects.requireNonNullElse(cur.getData(), new ArrayList<T>()).stream()); | ||
| if (!Objects.equals(prev.getHash(), cur.getHashPreviousBlock()) | ||
| && dataIdentityValid(concatData.collect(Collectors.toList()))) { | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| private boolean dataIdentityValid(List<T> 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<T> lastEntry = getLastEntry(); | ||
| return lastEntry | ||
| .map(t -> t.id() < data.id()) | ||
| .orElse(true); | ||
| } | ||
|
|
||
| private Optional<T> getLastEntry() { | ||
| return blocks.stream() | ||
| .map(Block::getData) | ||
| .filter(List::isEmpty) | ||
| .flatMap(Collection::stream) | ||
| .limit(1) | ||
| .findFirst(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,51 @@ | ||
| package blockchain; | ||
|
|
||
| import blockchain.crypto.Sign; | ||
| 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; | ||
| 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 final static AtomicLong minersIdGenerator = new AtomicLong(1); | ||
|
|
||
| public static void main(String[] args) throws Exception { | ||
|
|
||
| Sign sign = new Sign("keys/privateKey", "keys/publicKey"); | ||
|
|
||
| Persister<Message> persister = new FilePersister<>(OUTPUT_FILE_NAME); | ||
| Blockchain<Message> blockchain = new Blockchain<>(persister, | ||
| new PlainMessageFormat()); | ||
|
|
||
| List<Thread> miners = Stream | ||
| .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); | ||
| messenger.sendMessage("Tom", "Hey, I'm first!"); | ||
| messenger.sendMessage("Sarah", "It's not fair!"); | ||
| 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"); | ||
|
|
||
| miners.forEach(Thread::interrupt); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good abstraction