-
Notifications
You must be signed in to change notification settings - Fork 6
Part six #11
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
Open
oleksiipet
wants to merge
15
commits into
hyperskill:master
Choose a base branch
from
oleksiipet:part-six-new
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Part six #11
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
fe8e821
First phase implementation done
opetenko ddac0ff
Second phase implementation done
opetenko 7078dff
Second phase implementation done
opetenko ee39a82
Third phase implementation done
opetenko 849cbc3
Fourth phase implementation done
opetenko 7daf197
Fifth phase implementation done
opetenko 3ae01f1
Fifth phase implementation done
opetenko 7bc0f21
fourth phase implementation done
opetenko bcc9223
fifth phase implementation done
opetenko ae3b3ab
fourth phase implementation done code review
opetenko de514b1
Merge remote-tracking branch 'origin/part-four' into part-five
opetenko 4a2da61
fourth phase implementation done code review
opetenko 4f2192b
fourth phase implementation done code review
opetenko 5457aab
final phase implementation done
opetenko 84a8a56
final phase implementation done fix code review comments
opetenko File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Binary file not shown.
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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, | ||
| 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; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,239 @@ | ||
| 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.function.BiFunction; | ||
| 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; | ||
| private BiFunction<Integer, String, Optional<T>> systemFeedback; | ||
|
|
||
| public Blockchain(Persister<T> persister, DataFormatter<T> dataFormatter, | ||
| SystemFeedback<T> systemFeedback) { | ||
| this.systemFeedback = systemFeedback; | ||
| 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)) { | ||
| applyFeedback(String.format("miner%d", newBlock.getMinerId())); | ||
| 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); | ||
| } 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(); | ||
| } | ||
|
|
||
| public Stream<T> data() { | ||
| return new ArrayList<>(blocks).stream() | ||
| .map(Block::getData) | ||
| .filter(List::isEmpty) | ||
| .flatMap(Collection::stream); | ||
| } | ||
|
|
||
| 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(), | ||
| 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(); | ||
| } | ||
|
|
||
| private void applyFeedback(String miner) { | ||
| Optional<T> feedback = systemFeedback.apply(uniqueIdentity(), miner); | ||
| feedback.ifPresent( | ||
| this::appendData | ||
| ); | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 null-avoiding :)