Commit bcd6927e authored by Michel Schudel's avatar Michel Schudel

opdracht geschoond.

parent b91afff6
......@@ -129,8 +129,8 @@ add these transactions to the blockchain later. This is similar to the MemPool c
Implement the `TransactionPool` class:
- Add a private field `Set<Transaction>` `currentTransactions`
- Add a method `addTransaction` that adds a new `Transaction` to the pool
- Add a method `getAllTransactions` that returns an Unmodifiable set of transactions.
- Implement method `addTransaction` that adds a new `Transaction` to the pool
- Implement method `getAllTransactions` that returns an Unmodifiable set of transactions.
In the `BlockChainService` there is a` createTransaction` function that receives new transaction data from the frontend and returns the blockheight of the block where the transaction will be entered.
......@@ -179,7 +179,7 @@ Now, implement the `mineNewBlock (..)` method on the `Blockchain` class, as foll
- `index` (long) -> index of the most recent block in the blockchain + 1.
- `timestamp` (long) -> now
- list of transactions (method parameter)
- `previousHash` (string). The hash of the most recent block. You can calculate the hash over the string representation of that block. There is a utility method in the `HashUtil` class:` String hash (Block block) `
- `previousHash` (string). The hash of the most recent block. We will calculate the hash over the json representation of that block. There is a utility method in the `blockchain` class:`createHashOf(Block block)` that you can use for this.
- `proof` (long). The new proof-of-work.
- Add the new block to the chain.
......@@ -305,8 +305,8 @@ The same applies to newly mined blocks: these must also be further propagated in
- Add a call to `network.notifyPeersOfNewBlock (..)` at the end of the `mine ()` method on the `BlockChainService` class. This sends new mined blocks to the network.
- The `BlockChainService` class also contains a method` newBlockReceived (Block block, String sourcePeer) `. This method is called when a new block arrives on the interface.
Implement this function:
- check whether the new block is valid with respect to the node's own blockchain. This is the same check as the validation of the blockchain itself: is the proof of work of the block correct, and does the previousHash match the hash of the last block?
- if the block is valid, add block to the blockchain;
- check whether the new block is valid with respect to the node's own blockchain. Implement and use the method `isNewBlockValid` on the `Blockchain` class. This is the same check as the validation of the blockchain itself: is the proof of work of the block correct, and does the previousHash match the hash of the last block?
- if the block is valid, add block to the blockchain (with a method `addBlock` on the `Blockchain` class);
- call to `network.notifyPeersOfNewBlock (..)` to propagate the block further in the network;
- store blockchain using the `genericRepository.save(newlyReceivedBlock)`.
- Remove all transactions that were in the last block from the transaction pool. After all, they have already been mined.
......
......@@ -3,6 +3,4 @@ export interface Transaction {
from: string;
to: string;
amount: number;
signature: string;
publicKey: string;
}
......@@ -20,8 +20,6 @@
<span class="badge badge-primary">from {{ transaction.from }}</span>
<span class="badge badge-secondary">to {{ transaction.to }}</span>
<span class="badge badge-info">{{ transaction.amount | currency: 'EUR' }}</span>
<span class="badge badge-light">{{ transaction.signature}}</span>
<span class="badge badge-light">{{ transaction.publicKey}}</span>
</div>
</ng-container>
</div>
......
......@@ -14,8 +14,6 @@ export class CreateTransactionComponent {
from: '',
to: '',
amount: 0,
signature: '',
publicKey: ''
};
constructor(private transactionService: TransactionService, private router: Router) { }
......
......@@ -13,16 +13,12 @@
<th>From</th>
<th>To</th>
<th>Amount</th>
<th>Signature</th>
<th>Public key</th>
</tr>
<tr *ngFor="let transaction of transactions; trackBy: trackByFn">
<td>{{ transaction.id }}</td>
<td>{{ transaction.from }}</td>
<td>{{ transaction.to }}</td>
<td>{{ transaction.amount | currency: 'EUR' }}</td>
<td>{{ transaction.signature}}</td>
<td>{{ transaction.publicKey}}</td>
</tr>
</table>
</div>
......@@ -12,8 +12,6 @@
<span class="badge badge-primary">from {{ transaction.from }}</span>
<span class="badge badge-secondary">to {{ transaction.to }}</span>
<span class="badge badge-success">{{ transaction.amount | currency: 'EUR' }}</span>
<span class="badge badge-success">{{ transaction.signature}}</span>
<span class="badge badge-success">{{ transaction.publicKey}}</span>
<span class="badge badge-danger float-right">Unconfirmed</span>
</p>
</li>
......@@ -24,8 +22,6 @@
<span class="badge badge-primary">from {{ transaction.from }}</span>
<span class="badge badge-secondary">to {{ transaction.to }}</span>
<span class="badge badge-success">{{ transaction.amount | currency: 'EUR' }}</span>
<span class="badge badge-success">{{ transaction.signature}}</span>
<span class="badge badge-success">{{ transaction.publicKey}}</span>
</p>
</li>
</ul>
......
......@@ -30,12 +30,13 @@ public class ApiRestController {
/**
* Creates a new transaction on this node.
* @param transaction the new transaction
* @param transactionInputDto the new transaction
* @return a string describing in which block the transaction will(probably) end up in
*/
@PostMapping("/api/createtransaction")
public String newTransaction(@RequestBody Transaction transaction) {
long l = blockchainService.createTransaction(transaction.getFrom(), transaction.getTo(), transaction.getAmount());
public String newTransaction(@RequestBody TransactionInputDto transactionInputDto) {
//TODO implement me
long l = blockchainService.createTransaction(transactionInputDto.getFrom(),transactionInputDto.getTo(),transactionInputDto.getAmount());
return "{\"message\": \"new transaction will be added to block " + l + "\"}";
}
......
package nl.craftsmen.blockchain.craftscoinnode;
import nl.craftsmen.blockchain.craftscoinnode.util.GenericRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
......
package nl.craftsmen.blockchain.craftscoinnode;
import java.math.BigDecimal;
public class TransactionInputDto {
private String from;
private String to;
private BigDecimal amount;
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
}
......@@ -13,55 +13,17 @@ import java.util.Set;
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public class Block {
private long index;
private long timestamp;
private Set<Transaction> transactions;
private long proof;
private String previousHash;
//TODO add fields
//default constructor needed for json deserialization
@SuppressWarnings({"unused", "WeakerAccess"})
public Block() {
}
Block(long index, long timestamp, Set<Transaction> transactions, long proof, String previousHash) {
this.index = index;
this.timestamp = timestamp;
this.transactions = new HashSet<>(transactions);
this.proof = proof;
this.previousHash = previousHash;
}
public long getIndex() {
return index;
}
public long getProof() {
return proof;
}
public long getTimestamp() {
return timestamp;
}
public Set<Transaction> getTransactions() {
return transactions;
}
//TODO add constructor
@Override
public String toString() {
return "Block{" +
"index=" + index +
", timestamp=" + timestamp +
", transactions=" + transactions +
", proof=" + proof +
", previousHash='" + previousHash + '\'' +
'}';
}
public String getPreviousHash() {
return previousHash;
//TODO add getters and setters
//TODO add toString()
}
}
......@@ -3,6 +3,8 @@ package nl.craftsmen.blockchain.craftscoinnode.blockchain;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import nl.craftsmen.blockchain.craftscoinnode.transaction.Transaction;
import org.apache.commons.codec.digest.DigestUtils;
......@@ -18,7 +20,7 @@ import java.util.stream.Stream;
@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, isGetterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
public class Blockchain {
private final List<Block> chain = new ArrayList<>();
//TODO add chain field
/**
* Factory method to create a new blockchain.
......@@ -26,12 +28,8 @@ public class Blockchain {
* @return a new blockchain containing a genesis block.
*/
static Blockchain create() {
Blockchain blockchain = new Blockchain();
Block block = new Block(0, Instant
.now()
.toEpochMilli(), Collections.emptySet(), 100, "1");
blockchain.addBlock(block);
return blockchain;
//TODO implement me
return null;
}
//default constructor, needed for json deserialization
......@@ -47,13 +45,8 @@ public class Blockchain {
* @return the new block.
*/
Block mineNewBlock(Set<Transaction> transactionsToBeIncluded, Transaction reward) {
Set<Transaction> transactionsToBeMined = Stream.concat(transactionsToBeIncluded.stream(),Stream.of(reward)).collect(Collectors.toSet());
String previousHash = createHashOf(this.getLastBlock());
Block block = new Block(getIndexOfLastBlock() + 1, Instant
.now()
.toEpochMilli(), transactionsToBeMined, this.proofOfWork(), previousHash);
chain.add(block);
return block;
//TODO implement me
return null;
}
/**
......@@ -62,7 +55,7 @@ public class Blockchain {
* @param block the block to be added.
*/
void addBlock(Block block) {
chain.add(block);
//TODO implement me
}
......@@ -72,30 +65,8 @@ public class Blockchain {
* @return index of the block height of the next block.
*/
long getIndexOfNextBlock() {
return getIndexOfLastBlock() + 1;
}
private long getIndexOfLastBlock() {
Block block = getLastBlock();
return block != null ? block.getIndex() : -1;
}
/**
* Gets the most recent block in the blockchain.
*
* @return the most recent block in the blockchain.
*/
private Block getLastBlock() {
return chain.size() > 0 ? chain.get(chain.size() - 1) : null;
}
/**
* Returns the blockheight.
*
* @return the blockheight.
*/
private long getBlockHeight() {
return chain.size();
//TODO implement me
return 0;
}
/**
......@@ -104,13 +75,8 @@ public class Blockchain {
* @return all transactions in the blockchain.
*/
List<Transaction> getAllTransactions() {
return this
.chain
.stream()
.flatMap(block -> block
.getTransactions()
.stream()
).collect(Collectors.toList());
//TODO implement me
return null;
}
/**
......@@ -120,15 +86,8 @@ public class Blockchain {
* @return transactions in the blockchain for a specific wallet.
*/
public List<Transaction> getTransactionsFor(String walletId) {
return this
.chain
.stream()
.flatMap(block -> block
.getTransactions()
.stream())
.filter(transaction -> transaction.hasParticipant(walletId)
).collect(Collectors.toList());
//TODO implement me
return null;
}
/**
......@@ -138,15 +97,8 @@ public class Blockchain {
* @return if this blockchain is valid.
*/
boolean isValid() {
for (int i = chain.size() - 1; i >= 1; i--) {
if (!chain.get(i).getPreviousHash().equals(createHashOf(chain.get(i - 1)))) {
return false;
}
if (!validProof(chain.get(i - 1).getProof(), chain.get(i).getProof())) {
return false;
}
}
return true;
//TODO implement me
return false;
}
/**
......@@ -157,9 +109,9 @@ public class Blockchain {
* @return true if the new block is valid, false otherwise.
*/
boolean isNewBlockValid(Block newBlock) {
Block lastBlock = getLastBlock();
return lastBlock == null || newBlock.getPreviousHash().equals(createHashOf(lastBlock)) && validProof(lastBlock.getProof(), newBlock.getProof());
}
//TODO implement me
return false;
}
/**
* Find the proof of work for the next block.
......@@ -168,41 +120,27 @@ public class Blockchain {
* - p is the previous proof, and p' is the new proof
*/
private long proofOfWork() {
long lastProof = getLastBlock().getProof();
long currentProof = 0;
while (!validProof(lastProof, currentProof)) {
currentProof++;
}
return currentProof;
}
//TODO implement me
return 0;
private boolean validProof(long previousProof, long currentProof) {
String hash = DigestUtils.sha256Hex(previousProof + "" + currentProof);
return hash.startsWith("0000");
}
boolean isInferiorTo(Blockchain otherChain) {
return ((otherChainIsHigher(otherChain) || otherChainIsOlder(otherChain)) && otherChain.isValid());
//TODO implement me
return false;
}
private boolean otherChainIsOlder(Blockchain otherChain) {
return Objects.requireNonNull(otherChain.getLastBlock()).getTimestamp() < this.getLastBlock().getTimestamp();
}
private boolean otherChainIsHigher(Blockchain otherChain) {
return otherChain.getBlockHeight() > this.getBlockHeight();
}
private String createHashOf(Block block) {
return DigestUtils.sha256Hex(block.toString());
ObjectMapper objectMapper= new ObjectMapper();
try {
String value = objectMapper.writeValueAsString(block);
return DigestUtils.sha256Hex(value);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
@Override
public String toString() {
return "Blockchain{" +
"chain=" + chain +
'}';
}
//TODO toString
}
......@@ -56,11 +56,7 @@ public class BlockchainService {
}
private void initializeBlockchain() {
this.blockchain = genericRepository.load(Blockchain.class).orElseGet(() -> {
Blockchain chain = Blockchain.create();
genericRepository.save(chain);
return chain;
});
//TODO implement me
}
/**
......@@ -69,7 +65,8 @@ public class BlockchainService {
* @return the blockchain of this node.
*/
public Blockchain retrieveBlockChain() {
return this.blockchain;
//TODO implement me
return null;
}
/**
......@@ -81,12 +78,8 @@ public class BlockchainService {
* @return the block height of the block that this transaction will be mined in.
*/
public long createTransaction(String from, String to, BigDecimal amount) {
LOGGER.info("creating new transaction: from: {}, to: {}, amount: {}", from, to, amount);
String signature = signatureService.sign(from + to + amount);
Transaction transaction = new Transaction(from, to, amount, signature, signatureService.getPublicKey());
transactionPool.addTransaction(transaction);
network.notifyPeersOfNewTransaction(transaction, instanceInfo.getNode());
return this.blockchain.getIndexOfNextBlock();
//TODO implement me
return 0;
}
/**
......@@ -95,7 +88,8 @@ public class BlockchainService {
* @return the pending transactions from the transaction pool.
*/
public Set<Transaction> getPendingTransactions() {
return Collections.unmodifiableSet(transactionPool.getAllTransactions());
//TODO implement me
return null;
}
/**
......@@ -104,18 +98,8 @@ public class BlockchainService {
* @return the new block that has just been mined.
*/
public Block mine() {
if (this.transactionPool.getAllTransactions().isEmpty()) {
throw new RuntimeException("no pending transactions, nothing to mine.");
}
//reward the miner
String signature = signatureService.sign("0" + miningWalletId + CRAFTSCOIN_MINING_REWARD);
Transaction reward = new Transaction("0", miningWalletId, CRAFTSCOIN_MINING_REWARD, signature, signatureService.getPublicKey());
transactionPool.addTransaction(reward);
Block newBlock = this.blockchain.mineNewBlock(transactionPool.getAllTransactions(), reward);
genericRepository.save(this.blockchain);
transactionPool.clearTransactions();
network.notifyPeersOfNewBlock(newBlock, null);
return newBlock;
//TODO implement me
return null;
}
/**
......@@ -123,15 +107,7 @@ public class BlockchainService {
* If another node has a blockchain that is 'better', the blockchain of the current node will be replaced.
*/
private void reachConsensus() {
List<Blockchain> otherChains = network.retrieveBlockchainsFromPeers();
LOGGER.info("{} blockchains from peers received. Checking...", otherChains.size());
Optional<Blockchain> betterChain = otherChains.stream().filter(b -> this.blockchain.isInferiorTo(b)).max(Comparator.comparing(Blockchain::getIndexOfNextBlock));
betterChain.ifPresent(d -> {
this.blockchain = d;
clearConfirmedTransactions();
genericRepository.save(this.blockchain);
});
LOGGER.info("Finished reaching consensus.");
//TODO implement me
}
/**
......@@ -142,19 +118,9 @@ public class BlockchainService {
* @param sourcePeer the peer that sent the notification.
*/
public void newTransactionReceived(Transaction transaction, String sourcePeer) {
LOGGER.info("received transaction: {} from source peer {}", transaction, sourcePeer);
if (!transactionPool.getAllTransactions().contains(transaction)) {
LOGGER.info("transaction {} is new, adding it to the list of pending transactions.", transaction);
boolean validTransaction = signatureService.verify(transaction.getFrom() + transaction.getTo() + transaction.getAmount(), transaction.getSignature(), transaction.getPublicKey());
if (validTransaction) {
LOGGER.info("transaction is valid, adding to the transaction pool.");
transactionPool.addTransaction(transaction);
} else {
LOGGER.error("transaction has no valid signature, discarding.");
}
network.notifyPeersOfNewTransaction(transaction, sourcePeer);
//TODO implement me
}
}
/**
* Adds a newly received block (if valid) to the blockchain and propagates it further into the network.
......@@ -163,23 +129,12 @@ public class BlockchainService {
* @param sourcePeer the peer that sent the notification.
*/
public void newBlockReceived(Block block, String sourcePeer) {
LOGGER.info("received new block: {}", block);
if (blockchain.isNewBlockValid(block)) {
LOGGER.info("block is valid, adding it to the blockchain.");
this.blockchain.addBlock(block);
this.network.notifyPeersOfNewBlock(block, sourcePeer);
genericRepository.save(this.blockchain);
clearConfirmedTransactions();
} else {
LOGGER.info("block is not valid with respect to my blockchain, discarding.");
}
//TODO implement me
}
private void clearConfirmedTransactions() {
LOGGER.info("removing all confirmed transactions from the transaction pool");
transactionPool.clearTransactions(blockchain.getAllTransactions());
//TODO implement me
}
/**
......
......@@ -12,13 +12,7 @@ import java.util.UUID;
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public class Transaction {
private UUID id;
private String from;
private String to;
private BigDecimal amount;
private String signature;
private String publicKey;
//TODO insert fields
//default public constructor, needed for json deserialization
@SuppressWarnings({"unused", "WeakerAccess"})
......@@ -26,72 +20,9 @@ public class Transaction {
}
public Transaction(String from, String to, BigDecimal amount, String signature, String publicKey) {
this.id = UUID.randomUUID();
this.to = to;
this.from = from;
this.amount = amount;
this.signature = signature;
this.publicKey = publicKey;
}
public String getFrom() {
return from;
}
public String getTo() {
return to;
}
public BigDecimal getAmount() {
return amount;
}
public String getPublicKey() {
return publicKey;
}
//TODO insert constructor
public String getSignature() {
return signature;
}
//TODO getters and setters
public boolean hasParticipant(String walletId) {
return isRecepient(walletId) || isSender(walletId);
}
public boolean isRecepient(String walletId) {
return to.equals(walletId);
}
public boolean isSender(String walletId) {
return from.equals(walletId);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Transaction that = (Transaction) o;
return id != null ? id.equals(that.id) : that.id == null;
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
@Override
public String toString() {
return "Transaction{" +
"id=" + id +
", from='" + from + '\'' +
", to='" + to + '\'' +
", amount=" + amount +
'}';
}
//TODO equals based on id, hashcode based on id, toString
}
......@@ -17,23 +17,19 @@ public class TransactionPool {
private static final Logger LOGGER = LoggerFactory.getLogger(TransactionPool.class);
private final Set<Transaction> transactions = new HashSet<>();
/**
* Add a new transaction to this pool.
* @param transaction the transaction.
*/
public void addTransaction(Transaction transaction) {
transactions.add(transaction);
LOGGER.info("transaction {} has been added to the pool", transaction);
//TODO implement me
}
/**
* Removes all transactions from the pool.
*/
public void clearTransactions() {
transactions.clear();
LOGGER.info("all transactions have been purged.");
//TODO implement me
}
/**
......@@ -41,15 +37,15 @@ public class TransactionPool {
* @param transactions the supplied transactions list.
*/
public void clearTransactions(List<Transaction> transactions) {
this.transactions.removeAll(transactions);
LOGGER.info("the following transactions have been purged: {}", transactions);
//TODO implement me
}
/**
* Gets all transactions from the pool.
* Gets all transactions from the pool in an unmodifiable list.
* @return sll transactions from the pool.
*/
public Set<Transaction> getAllTransactions() {
return Collections.unmodifiableSet(transactions);
//TODO implement me