Commit 3c9ae1df authored by Michel Schudel's avatar Michel Schudel

Refactored quite a bit.

parent e75eadf4
......@@ -22,4 +22,6 @@ build/
nbbuild/
dist/
nbdist/
.nb-gradle/
\ No newline at end of file
.nb-gradle/
out
......@@ -10,6 +10,10 @@ buildscript {
}
}
plugins {
id 'org.springframework.boot' version '1.5.9.RELEASE'
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
......
package nl.craftsmen.blockchain.blockchain;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
......@@ -12,7 +11,7 @@ public class Block {
private final long proof;
private final String previousHash;
public Block(long index,long timestamp, List<Transaction> transactions, long proof, String previousHash) {
public Block(long index, long timestamp, List<Transaction> transactions, long proof, String previousHash) {
this.index = index;
this.timestamp = timestamp;
this.transactions = new ArrayList<>(transactions);
......
package nl.craftsmen.blockchain.blockchain;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.codec.binary.Hex;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import javax.annotation.PostConstruct;
import java.math.BigDecimal;
import java.security.MessageDigest;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Component
public class Blockchain {
@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, isGetterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
public class BlockChain {
private List<Block> chain = new ArrayList<>();
private List<Transaction> currentTransactions = new ArrayList<>();
private Set<String> nodes = new HashSet<>();
private RestTemplate restTemplate = new RestTemplate();
@PostConstruct
public void initFirstBlock() {
newBlock(100, "1");
//default constructor, needed for json deserialization
public BlockChain() {
}
private Block newBlock(long proof, String previousHash) {
Block block = new Block(getIndexOfLastBlock(), Instant.now().toEpochMilli(), currentTransactions, proof, previousHash);
public Block createNewBlock(long proof, String previousHash, List<Transaction> transactionsToBeIncluded) {
Block block = new Block(getIndexOfLastBlock(), Instant
.now()
.toEpochMilli(), transactionsToBeIncluded, proof, previousHash);
chain.add(block);
currentTransactions.clear();
return block;
}
public List<Block> getBlockchain() {
return Collections.unmodifiableList(chain);
}
public long newTransaction(Transaction transaction) {
currentTransactions.add(transaction);
//notify the other nodes of this transaction
for (String node : nodes) {
restTemplate.postForEntity(node + "/newtransaction", transaction, Object.class);
}
return getIndexOfLastBlock();
}
public Block mine() {
long lastProof = getLastBlock().get().getProof();
long newProof = proofOfWork(lastProof);
//reward the miner
Transaction transaction = new Transaction("0", "this-node", BigDecimal.valueOf(1));
newTransaction(transaction);
String previousHash = hash(getLastBlock().get());
Block newBlock = newBlock(newProof, previousHash);
for (String node: nodes) {
restTemplate.postForEntity(node + "newblock", newBlock, null);
}
return newBlock;
public void addBlock(Block block) {
//mind you, no validation whatsoever is done here. Probably should check the validity of the received block.
chain.add(block);
}
private long getIndexOfLastBlock() {
public long getIndexOfLastBlock() {
Optional<Block> block = getLastBlock();
return block.map(block1 -> block1.getIndex() + 1).orElse(1L);
return block
.map(block1 -> block1.getIndex() + 1)
.orElse(1L);
}
private Optional<Block> getLastBlock() {
public Optional<Block> getLastBlock() {
return chain.size() == 0 ? Optional.empty() : Optional.of(chain.get(chain.size() - 1));
}
private long proofOfWork(long lastProof) {
/*Simple Proof of Work Algorithm:
- Find a number p' such that hash(pp') contains leading 4 zeroes, where p is the previous p'
- p is the previous proof, and p' is the new proof*/
long nr = 0;
String hash;
do {
hash = createHashAsHexString(lastProof + "" + nr);
nr++;
} while (!validProof(hash));
return nr;
public long getBlockHeight() {
return chain.size();
}
private boolean validProof(String hash) {
return hash.startsWith("0000");
}
private String hash(Block block) {
try {
ObjectMapper mapper = new ObjectMapper();
String blockString = mapper.writeValueAsString(block);
return createHashAsHexString(blockString);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
private String createHashAsHexString(String blockString) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(blockString.getBytes());
return new String(Hex.encodeHex(hash));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
public List<Transaction> getTransactionsFor(String walletId) {
List<Transaction> associatedTransactions = new ArrayList<>();
for (Block block : this.chain) {
for (Transaction transaction : block.getTransactions()) {
if (transaction.hasParticipant(walletId)) {
associatedTransactions.add(transaction);
}
}
}
return associatedTransactions;
}
public void registerNode(String address) {
nodes.add(address);
}
public boolean validChain(List<Block> chain) {
return true;
}
public void resolveConflicts() {
for (String node : nodes) {
List<Block> otherChain = restTemplate.getForObject(node + "blockchain", List.class);
if (otherChain.size() > chain.size() && validChain(otherChain)) {
this.chain = otherChain;
public boolean isValid() {
for (int i = chain.size() - 1; i >= 1; i--) {
if (!chain.get(i).getPreviousHash().equals(HashUtil.hash(chain.get(i - 1)))) {
return false;
}
if (!HashUtil.validProof(chain.get(i - 1).getProof(), chain.get(i).getProof())) {
return false;
}
}
}
public void insertNewBlock(Block block) {
//mind you, no validation whatsoever is done here. Probably should check the validity of the received block.
chain.add(block);
return true;
}
}
package nl.craftsmen.blockchain.blockchain;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
@Component
public class BlockchainLogic {
private static final Logger LOGGER = LoggerFactory.getLogger(BlockchainLogic.class);
private List<Transaction> currentTransactions = new ArrayList<>();
private BlockChain blockChain;
private Network network;
@Autowired
public BlockchainLogic(Network network) {
this.blockChain = new BlockChain();
this.network = network;
}
@PostConstruct
public void initFirstBlock() {
blockChain.createNewBlock(100, "1", this.currentTransactions);
}
public BlockChain getBlockchain() {
return blockChain;
}
public long newTransaction(Transaction transaction) {
transaction.init();
LOGGER.info("received new transaction: {}", transaction);
currentTransactions.add(transaction);
//notify the other nodes of this transaction
network.notifyNewTransaction(transaction);
return blockChain.getIndexOfLastBlock();
}
public Block mine() {
long lastProof = blockChain.getLastBlock()
.get()
.getProof();
long newProof = proofOfWork(lastProof);
//reward the miner
Transaction transaction = new Transaction("0", "this-node", BigDecimal.valueOf(1));
currentTransactions.add(transaction);
String previousHash = HashUtil.hash(blockChain.getLastBlock().get());
Block newBlock = blockChain.createNewBlock(newProof, previousHash, this.currentTransactions);
currentTransactions.clear();
network.notifyNewBlock(newBlock);
return newBlock;
}
private long proofOfWork(long lastProof) {
/*Simple Proof of Work Algorithm:
- Find a number p' such that hash(pp') contains leading 4 zeroes, where p is the previous p'
- p is the previous proof, and p' is the new proof*/
long currentProof = 0;
while (!HashUtil.validProof(lastProof, currentProof)) {
currentProof++;
}
return currentProof;
}
public void resolveConflicts() {
List<BlockChain> otherChains = network.getBlockchains();
for (BlockChain otherChain : otherChains) {
if (otherChain.getBlockHeight() > blockChain.getBlockHeight() && otherChain.isValid()) {
this.blockChain = otherChain;
break;
}
}
}
public void insertNewBlock(Block block) {
blockChain.addBlock(block);
}
public boolean isValid() {
return blockChain.isValid();
}
public WalletDto getWallet(String walletName) {
BigDecimal balance = BigDecimal.ZERO;
List<Transaction> unconfirmedTransactions = new ArrayList<>();
List<Transaction> confirmedTransactions = blockChain.getTransactionsFor(walletName);
for (Transaction transaction : confirmedTransactions) {
if (transaction.isRecepient(walletName)) {
balance = balance.add(transaction.getAmount());
} else if (transaction.isSender(walletName)) {
balance = balance.subtract(transaction.getAmount());
}
}
//unconfirmed transactions
for (Transaction transaction : this.currentTransactions) {
if (!confirmedTransactions.contains(transaction) && transaction.getTo().equals(walletName)) {
balance = balance.add(transaction.getAmount());
unconfirmedTransactions.add(transaction);
} else if (!confirmedTransactions.contains(transaction) && transaction.getFrom().equals(walletName)) {
balance = balance.subtract(transaction.getAmount());
unconfirmedTransactions.add(transaction);
}
}
return new WalletDto(balance, confirmedTransactions, unconfirmedTransactions);
}
}
package nl.craftsmen.blockchain.blockchain;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import org.springframework.web.bind.annotation.*;
@RestController
public class BlockchainRestController {
@Autowired
private Blockchain blockchain;
private BlockchainLogic blockchainLogic;
@Autowired
private Network network;
@PostMapping("newtransaction")
public String newTransaction(@RequestBody Transaction transaction) {
long l = blockchain.newTransaction(transaction);
long l = blockchainLogic.newTransaction(transaction);
return "{\"message\": \"new transaction will be added to block " + l + "\"}";
}
@PostMapping("mine")
public Block mine() {
return blockchain.mine();
return blockchainLogic.mine();
}
@PostMapping("registernode")
public void registerNode(@RequestBody List<String> addresses) {
addresses.forEach(blockchain::registerNode);
@PostMapping("registernodes")
public void registerNode(@RequestBody String node) {
network.registerNewNode(node);
}
@GetMapping("blockchain")
public List<Block> blockchain() {
return blockchain.getBlockchain();
public BlockChain blockchain() {
return blockchainLogic.getBlockchain();
}
@PostMapping
public void newBlock(@RequestBody Block block) {
blockchain.insertNewBlock(block);
blockchainLogic.insertNewBlock(block);
}
@GetMapping("resolve")
public void resolve() {
blockchain.resolveConflicts();
blockchainLogic.resolveConflicts();
}
@GetMapping("valid")
public boolean isValid() {
return blockchainLogic.isValid();
}
@GetMapping("wallet/{walletId}")
public WalletDto getWallet(@PathVariable String walletId) {
return blockchainLogic.getWallet(walletId);
}
}
package nl.craftsmen.blockchain.blockchain;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.codec.binary.Hex;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class HashUtil {
public static String hash(Block block) {
try {
ObjectMapper mapper = new ObjectMapper();
String blockString = mapper.writeValueAsString(block);
return createHashAsHexString(blockString);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
public static String createHashAsHexString(String blockString) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(blockString.getBytes());
return new String(Hex.encodeHex(hash));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public static boolean validProof( long previousProof, long currentProof) {
String hash = HashUtil.createHashAsHexString(previousProof + "" + currentProof);
return hash.startsWith("0000");
}
}
package nl.craftsmen.blockchain.blockchain;
import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import java.net.Inet4Address;
import java.net.UnknownHostException;
@Component
public class InstanceInfo implements ApplicationListener<EmbeddedServletContainerInitializedEvent> {
private String node;
@Override
public void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) {
if (node == null) {
int thePort = event.getEmbeddedServletContainer().getPort();
try {
String host = Inet4Address
.getLocalHost()
.getHostAddress();
this.node = host + ":" + thePort;
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
}
}
public String getNode() {
return node;
}
}
package nl.craftsmen.blockchain.blockchain;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@Component
public class Network {
private Set<String> nodes = new HashSet<>();
private RestTemplate restTemplate = new RestTemplate();
private InstanceInfo instanceInfo;
@Autowired
public Network(InstanceInfo instanceInfo) {
this.instanceInfo = instanceInfo;
}
public void notifyNewTransaction(Transaction transaction) {
for (String node : nodes) {
restTemplate.postForEntity(node + "/newtransaction", transaction, Object.class);
}
}
public void notifyNewBlock(Block newBlock) {
for (String node : nodes) {
restTemplate.postForEntity(node + "newblock", newBlock, null);
}
}
public Set<String> registerNewNode(String newNode) {
if (!nodes.contains(newNode)) {
for (String node : nodes) {
ResponseEntity<Set> listResponseEntity = restTemplate.postForEntity(node + "registernode", newNode, Set.class, (Object) null);
Set<String> list = listResponseEntity.getBody();
nodes.addAll(list.stream().filter(e -> !e.equals(instanceInfo.getNode())).collect(Collectors.toSet()));
}
nodes.add(newNode);
}
return nodes
.stream()
.filter(e -> !e.equals(newNode))
.collect(Collectors.toSet());
}
public List<BlockChain> getBlockchains() {
List<BlockChain> blockchainList = new ArrayList<>();
for (String node : nodes) {
BlockChain otherChain = restTemplate.getForObject(node + "blockchain", BlockChain.class);
blockchainList.add(otherChain);
}
return blockchainList;
}
}
package nl.craftsmen.blockchain.blockchain;
import java.math.BigDecimal;
import java.util.UUID;
public class Transaction {
private String sender;
private String recepient;
private UUID id;
private String from;
private String to;
private BigDecimal amount;
//default constructor, needed for json deserialization
public Transaction() {
}
public Transaction(String sender, String recepient, BigDecimal amount) {
this.sender = sender;
this.recepient = recepient;
public Transaction(String to, String recepient, BigDecimal amount) {
this.to = to;
this.from = recepient;
this.amount = amount;
this.id = UUID.randomUUID();
}
public void setSender(String sender) {
this.sender = sender;
}
public void setRecepient(String recepient) {
this.recepient = recepient;
public void init() {
if (this.getId() == null) {
this.id = UUID.randomUUID();
}
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
public UUID getId() {
return id;
}
public String getSender() {
return sender;
public String getFrom() {
return from;
}
public String getRecepient() {
return recepient;
public String getTo() {
return to;
}
public BigDecimal getAmount() {
return amount;
}
public boolean hasParticipant(String walletId) {
return isRecepient(walletId) || isRecepient(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 +
'}';
}
}
package nl.craftsmen.blockchain.blockchain;
import java.math.BigDecimal;
import java.util.List;
public class WalletDto {
private BigDecimal balance;
private List<Transaction> minedTransactions;
private List<Transaction> unconfirmedTransactions;
public WalletDto(BigDecimal balance, List<Transaction> minedTransactions, List<Transaction> unconfirmedTransactions) {
this.balance = balance;
this.minedTransactions = minedTransactions;
this.unconfirmedTransactions = unconfirmedTransactions;
}
public BigDecimal getBalance() {
return balance;
}
public List<Transaction> getMinedTransactions() {
return minedTransactions;
}
public List<Transaction> getUnconfirmedTransactions() {
return unconfirmedTransactions;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<Configuration monitorInterval="30">
<Properties>
<Pro