Commit fe701f2f authored by Michel Schudel's avatar Michel Schudel

added a bunch of javadoc.

parent 8b87e2a3
This diff is collapsed.
......@@ -5,7 +5,7 @@ import nl.craftsmen.blockchain.craftscoinnode.blockchain.Blockchain;
import nl.craftsmen.blockchain.craftscoinnode.blockchain.BlockchainService;
import nl.craftsmen.blockchain.craftscoinnode.network.Network;
import nl.craftsmen.blockchain.craftscoinnode.transaction.Transaction;
import nl.craftsmen.blockchain.craftscoinnode.wallet.WalletDto;
import nl.craftsmen.blockchain.craftscoinnode.wallet.Wallet;
import nl.craftsmen.blockchain.craftscoinnode.wallet.WalletService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
......@@ -25,14 +25,13 @@ public class CraftsCoinRestController {
private Network network;
/**
* Processes a new transaction, either from this node or a transaction from another peer.
* @param peer the peer (optional)
* @param transaction the new transactions
* Creates a new transaction on this node.
* @param transaction the new transaction
* @return a string describing in which block the transaction will(probably) end up in
*/
@PostMapping("/api/newtransaction")
public String newTransaction(@RequestHeader(value = "peer", required = false) String peer, @RequestBody Transaction transaction) {
long l = blockchainService.newTransaction(transaction, peer);
public String newTransaction(@RequestBody Transaction transaction) {
long l = blockchainService.createTransaction(transaction.getFrom(), transaction.getTo(), transaction.getAmount());
return "{\"message\": \"new transaction will be added to block " + l + "\"}";
}
......@@ -92,6 +91,16 @@ public class CraftsCoinRestController {
blockchainService.newBlockReceived(block, peer);
}
/**
* Processes a transaction received from a peer in the network.
* @param peer the peer (optional)
* @param transaction the received transaction
*/
@PostMapping("/api/addtransaction")
public void addTransaction(@RequestHeader(value = "peer", required = false) String peer, @RequestBody Transaction transaction) {
blockchainService.newTransactionReceived(transaction, peer);
}
/**
* Checks the validity of the blockchain of this node
* @return true or false
......@@ -107,7 +116,7 @@ public class CraftsCoinRestController {
* @return the balance, confirmed, and unconfirmed transactions.
*/
@GetMapping("/api/wallet/{walletId}")
public WalletDto getWallet(@PathVariable String walletId) {
public Wallet getWallet(@PathVariable String walletId) {
return walletService.getWallet(walletId);
}
......
......@@ -11,23 +11,38 @@ import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Implementation of the blockchain.
*/
@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, isGetterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
public class Blockchain {
private List<Block> chain = new ArrayList<>();
/**
* Factory method to create a new blockchain.
* @return a new blockchain containing a genesis block.
*/
public static Blockchain create() {
Blockchain blockchain = new Blockchain();
blockchain.createNewBlock(100, "1", Collections.emptySet());
return blockchain;
}
//default constructor, needed for json deserialization
@SuppressWarnings({"unused", "WeakerAccess"})
public Blockchain() {
}
/**
* Creates a new block.
* @param proof proof-of-work of this new block.
* @param previousHash the hash of the previous block.
* @param transactionsToBeIncluded the transactions the be included in the block.
* @return the new block.
*/
public Block createNewBlock(long proof, String previousHash, Set<Transaction> transactionsToBeIncluded) {
Block block = new Block(getIndexOfLastBlock() + 1, Instant
.now()
......@@ -36,11 +51,19 @@ public class Blockchain {
return block;
}
/**
* Adds block to the blockchain.
* @param block the block to be added.
*/
public void addBlock(Block block) {
chain.add(block);
}
/**
* Gets the index of the block height of the next block.
* @return index of the block height of the next block.
*/
public long getIndexOfNextBlock() {
return getIndexOfLastBlock() + 1;
}
......@@ -50,14 +73,26 @@ public class Blockchain {
return block != null ? block.getIndex() : -1;
}
/**
* Gets the most recent block in the blockchain.
* @return the most recent block in the blockchain.
*/
public Block getLastBlock() {
return chain.size() == 0 ? null : chain.get(chain.size() - 1);
}
/**
* Returns the blockheight.
* @return the blockheight.
*/
public long getBlockHeight() {
return chain.size();
}
/**
* Gets all transactions in the blockchain.
* @return all transactions in the blockchain.
*/
public List<Transaction> getAllTransactions() {
return this
.chain
......@@ -68,6 +103,11 @@ public class Blockchain {
).collect(Collectors.toList());
}
/**
* Gets transactions in the blockchain for a specific wallet.
* @param walletId the walletId.
* @return transactions in the blockchain for a specific wallet.
*/
public List<Transaction> getTransactionsFor(String walletId) {
return this
.chain
......@@ -80,6 +120,10 @@ public class Blockchain {
}
/**
* Checks if this blockchain is valid.
* @return if this blockchain is valid.
*/
public boolean isValid() {
for (int i = chain.size() - 1; i >= 1; i--) {
if (!chain.get(i).getPreviousHash().equals(HashUtil.hash(chain.get(i - 1)))) {
......@@ -92,11 +136,19 @@ public class Blockchain {
return true;
}
/**
* Checks if a new block is valid with respect to this blockchain.
* @param newBlock the new block.
* @return true if the new block is valid, false otherwise.
*/
public boolean isNewBlockValid(Block newBlock) {
Block lastBlock = getLastBlock();
return newBlock.getPreviousHash().equals(HashUtil.hash(lastBlock)) && validProof(lastBlock.getProof(), newBlock.getProof());
}
/**
* Find the proof of work for the next block.
*/
public long proofOfWork() {
long lastProof = getLastBlock().getProof();
/*Simple Proof of Work Algorithm:
......
......@@ -11,6 +11,10 @@ import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
/**
* Persistence functionality for the blockchain.
*/
@Component
final class BlockchainRepository {
......@@ -24,6 +28,10 @@ final class BlockchainRepository {
this.instanceInfo = instanceInfo;
}
/**
* attempts the load the blockchain from disk.
* @return the blockchain, or null if no blockchain was found on disk.
*/
Blockchain loadBlockChain() {
try {
File file = createBlockchainFileForThisNode();
......@@ -42,6 +50,10 @@ final class BlockchainRepository {
}
}
/**
* Saves the blockchain to disk.
* @param blockchain the blockchain.
*/
void saveBlockChain(Blockchain blockchain) {
try {
File file = createBlockchainFileForThisNode();
......
......@@ -3,6 +3,7 @@ package nl.craftsmen.blockchain.craftscoinnode.blockchain;
import nl.craftsmen.blockchain.craftscoinnode.network.Network;
import nl.craftsmen.blockchain.craftscoinnode.transaction.Transaction;
import nl.craftsmen.blockchain.craftscoinnode.transaction.TransactionPool;
import nl.craftsmen.blockchain.craftscoinnode.util.InstanceInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -15,6 +16,9 @@ import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* Manages the blockchain on this node.
*/
@Component
public class BlockchainService {
......@@ -26,18 +30,23 @@ public class BlockchainService {
private Network network;
private BlockchainRepository blockchainRepository;
private TransactionPool transactionPool;
private InstanceInfo instanceInfo;
@Value("${miningWalletId}")
private String miningWalletId;
@Autowired
public BlockchainService(Network network, BlockchainRepository blockchainRepository, TransactionPool transactionPool) {
public BlockchainService(Network network, BlockchainRepository blockchainRepository, TransactionPool transactionPool, InstanceInfo instanceInfo) {
this.network = network;
this.blockchainRepository = blockchainRepository;
this.transactionPool = transactionPool;
this.instanceInfo = instanceInfo;
}
/**
* Initializes and updates the blockchain on startup.
*/
@PostConstruct
public void init() {
network.connectToNetwork();
......@@ -55,25 +64,43 @@ public class BlockchainService {
}
}
/**
* Retrieves the blockchain of this node for API usage.
* @return the blockchain of this node.
*/
public Blockchain retrieveBlockChain() {
return this.blockchain;
}
public long newTransaction(Transaction transaction, String sourcePeer) {
LOGGER.info("received transaction: {} from source peer {}", transaction, sourcePeer);
if (transaction.getId() == null) {
transaction = new Transaction(transaction.getFrom(), transaction.getTo(), transaction.getAmount());
}
if (!transactionPool.getCurrentTransactions().contains(transaction)) {
LOGGER.info("transaction {} is new, adding it to the list of pending transactions.", transaction);
transactionPool.addTransaction(transaction);
network.notifyPeersOfNewTransaction(transaction, sourcePeer);
}
/**
* Creates a new transaction.
* @param from where the money comes from
* @param to where the money should be sent
* @param amount how much
* @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);
Transaction transaction = new Transaction(from, to, amount);
transactionPool.addTransaction(transaction);
network.notifyPeersOfNewTransaction(transaction, instanceInfo.getNode());
return this.blockchain.getIndexOfNextBlock();
}
/**
* Gets pending transactions for API usage.
* @return the pending transactions from the transaction pool.
*/
public Set<Transaction> getPendingTransactions() {
return Collections.unmodifiableSet(transactionPool.getAllTransactions());
}
/**
* Mines a new block.
* @return the new block that has just been mined.
*/
public Block mine() {
if (this.transactionPool.getCurrentTransactions().isEmpty()) {
if (this.transactionPool.getAllTransactions().isEmpty()) {
throw new RuntimeException("no pending transactions, nothing to mine.");
}
long newProof = blockchain.proofOfWork();
......@@ -82,13 +109,16 @@ public class BlockchainService {
Transaction transaction = new Transaction("0", miningWalletId, CRAFTSCOIN_MINING_REWARD);
transactionPool.addTransaction(transaction);
String previousHash = HashUtil.hash(this.blockchain.getLastBlock());
Block newBlock = this.blockchain.createNewBlock(newProof, previousHash, transactionPool.getCurrentTransactions());
Block newBlock = this.blockchain.createNewBlock(newProof, previousHash, transactionPool.getAllTransactions());
blockchainRepository.saveBlockChain(this.blockchain);
transactionPool.clearTransactions();
network.notifyPeersOfNewBlock(newBlock, null);
return newBlock;
}
/**
* Establishes consensus over the current state of the blockchain.
*/
private void reachConsensus() {
List<Blockchain> otherChains = network.retrieveBlockchainsFromPeers();
LOGGER.info("blockchains from peers received. Checking...");
......@@ -112,6 +142,25 @@ public class BlockchainService {
return otherChain.getBlockHeight() > this.blockchain.getBlockHeight();
}
/**
* Adds a newly received transaction to the pool and propagates it further into the network.
* @param transaction the new transaction.
* @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);
transactionPool.addTransaction(transaction);
network.notifyPeersOfNewTransaction(transaction, sourcePeer);
}
}
/**
* Adds a newly received block to the blockchain and propagates it further into the network.
* @param block the new block.
* @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)) {
......@@ -126,16 +175,18 @@ public class BlockchainService {
}
}
private void clearConfirmedTransactions() {
LOGGER.info("removing all confirmed transactions from the transaction pool");
transactionPool.clearTransactions(blockchain.getAllTransactions());
}
/**
* Returns if the blockchain on this node is valid.
* @return if the blockchain on this node is valid.
*/
public boolean isValid() {
return this.blockchain.isValid();
}
public Set<Transaction> getPendingTransactions() {
return Collections.unmodifiableSet(transactionPool.getCurrentTransactions());
}
}
......@@ -9,6 +9,11 @@ import java.security.NoSuchAlgorithmException;
class HashUtil {
/**
* Produces a SHA-256 hex-encoded hash string of a block.
* @param block the block.
* @return a has string in Hex format, like 'da348aff2d'
*/
static String hash(Block block) {
try {
ObjectMapper mapper = new ObjectMapper();
......@@ -21,6 +26,11 @@ class HashUtil {
}
/**
* Produces a SHA-256 hex-encoded hash string of a string.
* @param aString the string.
* @return a has string in Hex format, like 'da348aff2d'
*/
static String createHash(String aString) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
......
......@@ -58,32 +58,17 @@ public class Network {
peers = peersRepository.loadPeers();
}
public void notifyPeersOfNewTransaction(Transaction transaction, String sourcePeer) {
Set<String> collect = peers.stream().filter(p -> !p.equals(sourcePeer)).collect(Collectors.toSet());
LOGGER.info("notifying the following peers of transaction {}: {}", transaction, collect);
for (String node : collect) {
HttpEntity<Transaction> entity = new HttpEntity<>(transaction, createHttpHeaders());
post(node, "/newtransaction", entity, new ParameterizedTypeReference<Object>() {
});
}
}
public void notifyPeersOfNewBlock(Block newBlock, String sourcePeer) {
Set<String> collect = peers.stream().filter(p -> !p.equals(sourcePeer)).collect(Collectors.toSet());
LOGGER.info("notifying the following peers of new block {}: {}", newBlock, collect);
for (String node : peers.stream().filter(p -> !p.equals(sourcePeer)).collect(Collectors.toSet())) {
HttpEntity<Block> entity = new HttpEntity<>(newBlock, createHttpHeaders());
post(node, "/newblock", entity, new ParameterizedTypeReference<Object>() {});
}
}
private HttpHeaders createHttpHeaders() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("peer", instanceInfo.getNode());
return httpHeaders;
/**
* Gets the list of known peers.
* @return the list of known peers.
*/
public Set<String> getPeers() {
return Collections.unmodifiableSet(peers);
}
/**
* Attempts the connect to the network. For this, the list of peers in the xxxxx-peers.json file is used. If no peers are known, the bootstrap peer is tried.
*/
public void connectToNetwork() {
if (peers.isEmpty()) {
LOGGER.info("no peers known yet, trying to register this node to the bootstrap peer: {}", createHostEndpoint());
......@@ -111,6 +96,30 @@ public class Network {
}
}
private String createHostEndpoint() {
return this.bootstrapPeerHost + ":" + bootstrapPeerPort;
}
private void registerThroughBootstrapNode() {
ResponseEntity<Set<String>> listResponseEntity = post(createHostEndpoint(), "/registernode", instanceInfo.getNode(), new ParameterizedTypeReference<Set<String>>() {
});
if (listResponseEntity != null) {
Set<String> list = listResponseEntity.getBody();
LOGGER.info("received peer list: {}", list);
LOGGER.info("adding peers to the list of known peers: {}", list);
peers.addAll(list);
peersRepository.savePeers(peers);
LOGGER.info("the following peers are now known to this node: {}", peers);
} else {
LOGGER.warn("bootstrap node not found. I'm operating this blockchain on my own right now!");
}
}
/**
* Registers a new peer on this node.
* @param newPeer the new peer.
* @return a list of known peers so the new peer can add them to its peers list.
*/
public Set<String> registerNewPeer(String newPeer) {
LOGGER.info("a new peer has connected to the network: {}", newPeer);
if (!peers.contains(newPeer)) {
......@@ -138,6 +147,47 @@ public class Network {
return peersForRemote;
}
/**
* Notify other peers of a new transaction.
* @param transaction transaction
* @param sourcePeer the peer that sent the transsaction.
*/
public void notifyPeersOfNewTransaction(Transaction transaction, String sourcePeer) {
Set<String> collect = peers.stream().filter(p -> !p.equals(sourcePeer)).collect(Collectors.toSet());
LOGGER.info("notifying the following peers of transaction {}: {}", transaction, collect);
for (String node : collect) {
HttpEntity<Transaction> entity = new HttpEntity<>(transaction, createHttpHeaders());
post(node, "/addtransaction", entity, new ParameterizedTypeReference<Object>() {
});
}
}
/**
* Notify other peers of a new block.
* @param newBlock the new block.
* @param sourcePeer the peer that sent the new block.
*/
public void notifyPeersOfNewBlock(Block newBlock, String sourcePeer) {
Set<String> collect = peers.stream().filter(p -> !p.equals(sourcePeer)).collect(Collectors.toSet());
LOGGER.info("notifying the following peers of new block {}: {}", newBlock, collect);
for (String node : peers.stream().filter(p -> !p.equals(sourcePeer)).collect(Collectors.toSet())) {
HttpEntity<Block> entity = new HttpEntity<>(newBlock, createHttpHeaders());
post(node, "/newblock", entity, new ParameterizedTypeReference<Object>() {});
}
}
private HttpHeaders createHttpHeaders() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("peer", instanceInfo.getNode());
return httpHeaders;
}
/**
* Gets the blockchain from all known peers.
* @return the list of blockchains.
*/
public List<Blockchain> retrieveBlockchainsFromPeers() {
List<Blockchain> blockchainList = new ArrayList<>();
LOGGER.info("asking the following peers for their blockhain: {}", peers);
......@@ -149,8 +199,6 @@ public class Network {
return blockchainList;
}
private <T> ResponseEntity<T> post(String peer, String action, Object object, ParameterizedTypeReference<T> typeRef) {
try {
HttpEntity<?> httpEntity;
......@@ -170,26 +218,7 @@ public class Network {
}
private String createHostEndpoint() {
return this.bootstrapPeerHost + ":" + bootstrapPeerPort;
}
private void registerThroughBootstrapNode() {
ResponseEntity<Set<String>> listResponseEntity = post(createHostEndpoint(), "/registernode", instanceInfo.getNode(), new ParameterizedTypeReference<Set<String>>() {
});
if (listResponseEntity != null) {
Set<String> list = listResponseEntity.getBody();
LOGGER.info("received peer list: {}", list);
LOGGER.info("adding peers to the list of known peers: {}", list);
peers.addAll(list);
peersRepository.savePeers(peers);
LOGGER.info("the following peers are now known to this node: {}", peers);
} else {
LOGGER.warn("bootstrap node not found. I'm operating this blockchain on my own right now!");
}
}
public Set<String> getPeers() {
return Collections.unmodifiableSet(peers);
}
}
......@@ -14,6 +14,9 @@ import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
/**
* Persistence functionality for the list of peers.
*/
@Component
final class PeersRepository {
......@@ -27,6 +30,10 @@ final class PeersRepository {
this.instanceInfo = instanceInfo;
}
/**
* Loads all known peers from disk.
* @return the set of known peers.
*/
Set<String> loadPeers() {
try {
File file = createPeersFileForThisNode();
......@@ -48,6 +55,10 @@ final class PeersRepository {
}
}
/**
* Saves known peers to disk.
* @param peers the set of known peers.
*/
void savePeers(Set<String> peers) {
try {
File file = createPeersFileForThisNode();
......
......@@ -17,11 +17,11 @@ public class Transaction {
//default public constructor, needed for json deserialization
@SuppressWarnings({"unused", "WeakerAccess"})
public Transaction() {
this.id = UUID.randomUUID();
}
public Transaction(String from, String to, BigDecimal amount) {
this();
this.id = UUID.randomUUID();
this.to = to;
this.from = from;
this.amount = amount;
......
......@@ -10,29 +10,47 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Manages pending transactions.
*/
@Component
public class TransactionPool {
private static final Logger LOGGER = LoggerFactory.getLogger(BlockchainService.class);
private Set<Transaction> currentTransactions = new HashSet<>();
private Set<Transaction> transactions = new HashSet<>();
/**
* Add a new transaction to this pool.
* @param transaction the transaction.
*/
public void addTransaction(Transaction transaction) {
currentTransactions.add(transaction);
transactions.add(transaction);
LOGGER.info("transaction {} has been added to the pool", transaction);
}
/**
* Removes all transactions from the pool.
*/
public void clearTransactions() {
currentTransactions.clear();
transactions.clear();
LOGGER.info("all transactions have been purged.");
}
/**
* Removes all transactions from the pool that are in the supplied transactions list.
* @param transactions the supplied transactions list.
*/
public void clearTransactions(List<Transaction> transactions) {
LOGGER.info("the following transactions have been purged: {}", transactions);
currentTransactions.removeAll(transactions);
this.transactions.removeAll(transactions);
}
public Set<Transaction> getCurrentTransactions() {
return Collections.unmodifiableSet(currentTransactions);
/**
* Gets all transactions from the pool.
* @return sll transactions from the pool.
*/
public Set<Transaction> getAllTransactions() {
return Collections.unmodifiableSet(transactions);
}
}
......@@ -6,13 +6,13 @@ import java.math.BigDecimal;
import java.util.List;
public class WalletDto {
public class Wallet {
private BigDecimal balance;
private List<Transaction> minedTransactions;
private List<Transaction> unconfirmedTransactions;
public WalletDto(BigDecimal balance, List<Transaction> minedTransactions, List<Transaction> unconfirmedTransactions) {
Wallet(BigDecimal balance, List<Transaction> minedTransactions, List<Transaction> unconfirmedTransactions) {
this.balance = balance;
this.minedTransactions = minedTransactions;
this.unconfirmedTransactions = unconfirmedTransactions;
......
......@@ -22,28 +22,33 @@ public class WalletService {
this.transactionPool = transactionPool;
}
public WalletDto getWallet(String walletName) {
/**
* Prodcues a wallet.