In: Computer Science
“Griffin Blockchain”
This project will involve the creation of a Blockchain miner that does all the following:
For this project, you will create the main functionality inside of an existing code base called BlockchainBasics. The BlockchainBasics code that you will begin with is provided along with this document.
BlockchainBasics, once completed, will allow you to run two instances of this app on two different ports that pass transactions to each other and mine blocks.
This app doesn’t save the blocks into a blockchain, but the central functionality you code into this app will be able to be plugged in directly into a larger Blockchain application (the code for this will be provided in the coming weeks) that fully manages a Blockchain, connects to multiple networked nodes, and allows you to trade items on the network.
However, you will only be turning in the BlockchainBasics code as specified at the end of this doc.
This project will involve the following:
- Socket programming
- Queue
- Multithreading
- Hashing: SHA-256
- Merkle Trees
- Blockchain Proof of Work
####################
### ADD CODE HERE ###
####################
"[miner] Aborted mining block, probably because another confirmed block received."
java -jar BlockchainBasics.jar
Solution:
Givendata:
This project will involve the creation of a Blockchain miner that does all the following:
For this project, you will create the main functionality inside of an existing code base called BlockchainBasics. The BlockchainBasics code that you will begin with is provided along with this document.
Answer:
Program Files
Block.java
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
/**
* This holds the values for a Block in the Blockchain, and it has
methods to
* compute the Merkle Root and generate a hash.
*/
public class Block {
private String sMerkleRoot;
private int iDifficulty = 5; // Mining seconds in testing 5:
6,10,15,17,20,32 | testing 6: 12,289,218
private String sNonce;
private String sMinerUsername;
private String sHash;
/**
* This computes the Merkle Root. It either accepts 2 or 4 items, or
if made to
* be dynamic, then accepts any multiple of 2 (2,4,8.16.32,etc.)
items.
*
* @param lstItems
* @return
*/
public synchronized String
computeMerkleRoot(ArrayList<String> lstItems) {
BlockchainUtil oUtil = new BlockchainUtil();
String left;
String right;
Queue<String> qCompute = new LinkedList<String>();
for (int i = 0; i < lstItems.size(); i++) {
left = oUtil.generateHash(lstItems.get(i));
qCompute.add(left);
}
while (qCompute.size() > 1) {
left = qCompute.remove();
right = qCompute.remove();
qCompute.add(oUtil.generateHash(left + right));
}
return qCompute.remove();
}
/**
* This method populates a Merkle node's left, right, and hash
variables.
*
* @param oNode
* @param oLeftNode
* @param oRightNode
*/
private void populateMerkleNode(MerkleNode oNode, MerkleNode
oLeftNode, MerkleNode oRightNode) {
oNode.sHash = new BlockchainUtil().generateHash(oLeftNode.sHash + oRightNode.sHash);
}
// Hash this block, and hash will also be next block's previous hash.
/**
* This generates the hash for this block by combining the
properties and
* hashing them.
*
* @return
*/
public String computeHash() {
return new BlockchainUtil().generateHash(sMerkleRoot +
iDifficulty + sMinerUsername + sNonce);
}
public int getDifficulty() {
return iDifficulty;
}
public String getNonce() {
return sNonce;
}
public void setNonce(String nonce) {
this.sNonce = nonce;
}
public void setMinerUsername(String sMinerUsername) {
this.sMinerUsername = sMinerUsername;
}
public String getHash() {
return sHash;
}
public void setHash(String h) {
this.sHash = h;
}
public synchronized void setMerkleRoot(String merkleRoot)
{
this.sMerkleRoot = merkleRoot;
}
/**
* Run this to test your merkle tree logic.
*
* @param args
*/
public static void main(String[] args) {
ArrayList<String> lstItems = new
ArrayList<>();
Block oBlock = new Block();
String sMerkleRoot;
// These merkle root hashes based on "t1","t2" for two items,
and then "t3","t4"
// added for four items.
String sExpectedMerkleRoot_2Items =
"3269f5f93615478d3d2b4a32023126ff1bf47ebc54c2c96651d2ac72e1c5e235";
String sExpectedMerkleRoot_4Items =
"e08f7b0331197112ff8aa7acdb4ecab1cfb9497cbfb84fb6d54f1cfdb0579d69";
lstItems.add("t1");
lstItems.add("t2");
// *** BEGIN TEST 2 ITEMS ***
sMerkleRoot = oBlock.computeMerkleRoot(lstItems);
if (sMerkleRoot.equals(sExpectedMerkleRoot_2Items)) {
System.out.println("Merkle root method for 2 items
worked!");
}
else {
System.out.println("Merkle root method for 2 items failed!");
System.out.println("Expected: " +
sExpectedMerkleRoot_2Items);
System.out.println("Received: " + sMerkleRoot);
}
// *** BEGIN TEST 4 ITEMS ***
lstItems.add("t3");
lstItems.add("t4");
sMerkleRoot = oBlock.computeMerkleRoot(lstItems);
if (sMerkleRoot.equals(sExpectedMerkleRoot_4Items)) {
System.out.println("Merkle root method for 4 items
worked!");
}
else {
System.out.println("Merkle root method for 4 items failed!");
System.out.println("Expected: " +
sExpectedMerkleRoot_4Items);
System.out.println("Received: " + sMerkleRoot);
}
}
}
BlockChainBasics.java
/**
* This class manages the main thread, kicks off the Blockchain
management classes, and controls the UI interaction.
*/
public class BlockchainBasics {
public static String sRemoteMinerIP = null;
public static int iRemoteMinerPort = 0;
/**
* This method manages the UI interaction and spawns off the Miner
and P2PServer threads.
* @param args
*/
public static void main(String[] args){
BlockchainUtil u = new BlockchainUtil();
// Kick off miner.
String sUsername = u.promptUser("Username for this miner? ");
Miner oMiner = new Miner();
oMiner.sUsername = sUsername;
Thread oMinerThread = new Thread(oMiner);
oMinerThread.start();
// Sleep here so that miner thread can get up and running
completely.
u.sleep(500);
// Start server after getting port from user.
String sPort = u.promptUser("What port do you want to start server
on? ");
int iPort = Integer.parseInt(sPort);
P2PServer oServer = new P2PServer(iPort);
Thread oServerThread = new Thread(oServer);
oServerThread.start();
// Sleep here so that server thread can get up and running
completely.
u.sleep(500);
String sSend = u.promptUser("Send transactions to another miner (y,n)? ");
if(sSend.equals("y")){
sRemoteMinerIP = u.promptUser("Miner's IP address? " );
String sTempPort = u.promptUser("Miner's Port? ");
iRemoteMinerPort = Integer.parseInt(sTempPort);
}
// Looping Menu: create & send TX OR create only.
while(true){
String sTransaction = u.promptUser("Enter Transaction: ");
if(sSend.equals("y")) {
String sReply = P2PUtil.connectForOneMessage(sRemoteMinerIP,
iRemoteMinerPort, sTransaction);
u.p("[main] Reply from server: " + sReply);
}
Miner.lstTransactionPool.add(sTransaction);
}
}
}
BlockChainUtil.java
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Scanner;
/**
* This class has utility methods used by the Blockchain
logic.
*/
public class BlockchainUtil {
/**
* This method produces an SHA-256 hash of the string input.
* @param sOriginal
* @return
*/
public static synchronized String generateHash(String
sOriginal){
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] btEncodedhash =
digest.digest(sOriginal.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (int i = 0; i < btEncodedhash.length; i++) {
sb.append(Integer.toString((btEncodedhash[i] & 0xff) + 0x100,
16).substring(1));
}
return sb.toString();
} catch (Exception ex) {
System.out.println("Error generating hash: " +
ex.getMessage());
return null;
}
}
/**
* This method can either use Scanner or JOptionPane approaches to
get user input.
* @param sQuestion
* @return
*/
public String promptUser(String sQuestion){
// Using input dialog:
//return JOptionPane.showInputDialog(sQuestion);
// Using Scanner:
System.out.print(sQuestion);
Scanner oCommandInput = new Scanner(System.in);
return oCommandInput.nextLine();
}
/**
* This method allows user to avoid extra typing and efficient code,
especially if this class variable is one letter
* such as u, thus:
* u.p("hello");
* @param sMessage
*/
public void p(String sMessage){
System.out.println(sMessage);
}
/**
* This is used by any thread that needs to sleep to allow updating
of static variables or to alleviate
* CPU usage on continuous looping.
* @param lMillis
*/
public void sleep(long lMillis){
try{
Thread.sleep(lMillis);
}
catch(Exception ex){
// do nothing.
}
}
}
MerkelNode.java
/**
* This class simply holds a single Merkle Node's values.
*/
public class MerkleNode {
String sHash;
MerkleNode oLeft;
MerkleNode oRight;
}
Miner.java
import java.util.ArrayList;
/**
* This class runs on separate thread and manages the transaction
queue and
* Block mining.
*/
public class Miner implements Runnable {
public static volatile P2PMessageQueue oIncomingMessageQueue = new P2PMessageQueue();
public static volatile boolean bAbortPoW = false;
public static volatile ArrayList<String> lstTransactionPool =
new ArrayList<>();
int iBlockTxSize = 4;
public String sUsername;
/**
* PoW is where miner keeps trying incrementing nonce until hash
begins with as
* many 0s as the difficulty specifies.
*
* @param oBlock
* @return
*/
public boolean doProofOfWork(Block oBlock) {
String sLeadingZero = "0";
for (int i = 1; i < oBlock.getDifficulty(); i++) {
sLeadingZero += "0";
}
oBlock.setNonce("0");
int temp;
while (oBlock.computeHash().startsWith(sLeadingZero) != true)
{
if (bAbortPoW) {
bAbortPoW = false;
System.out.println("[miner] Aborted mining block, probably because
another confirmed block received.");
return false;
} else {
temp = Integer.parseInt(oBlock.getNonce()) + 1;
oBlock.setNonce(Integer.toString(temp));
oBlock.setHash(oBlock.computeHash());
}
}
return true;
}
/**
* This thread monitors incoming messages, monitors the transaction
queue, and
* mines Block if enough transactions collected. Called as part of
Runnable
* interface and shouldn't be called directly by code.
*/
public void run() {
BlockchainUtil u = new BlockchainUtil();
u.p("Miner thread started.");
// *****************************
// *** Eternal Mining Loop *****
// Because miner always checking for next block to immediately work
on.
while (true) {
u.sleep(500);
while (oIncomingMessageQueue.hasNodes()) {
P2PMessage oMessage = oIncomingMessageQueue.dequeue();
lstTransactionPool.add(oMessage.getMessage());
}
// Check if transaction pool full and lock if it is.
if (lstTransactionPool.size() >= iBlockTxSize) {
Block oBlock = new Block();
oBlock.setMinerUsername(sUsername);
oBlock.computeHash();
String sMerkleRoot =
oBlock.computeMerkleRoot(lstTransactionPool);
oBlock.setMerkleRoot(sMerkleRoot);
boolean bMined = doProofOfWork(oBlock);
if (bMined) {
// Notify connected node.
if (BlockchainBasics.sRemoteMinerIP != null) {
P2PUtil.connectForOneMessage(BlockchainBasics.sRemoteMinerIP,
BlockchainBasics.iRemoteMinerPort,
"mined");
}
u.p("");
u.p("***********");
u.p("BLOCK MINED");
u.p("nonce: " + oBlock.getNonce());
u.p("hash: " + oBlock.getHash());
u.p("");
u.p("Transactions:");
for (int x = 0; x < lstTransactionPool.size(); x++) {
u.p("Tx " + x + ": " + lstTransactionPool.get(x));
}
u.p("***********");
} else {
u.p("[miner] Mining block failed.");
}
// Clear tx pool.
lstTransactionPool.clear();
}
}
}
}
P2PMessage.java
/**
* This class holds incoming messages to this node.
*/
public class P2PMessage {
private String sMessage;
// Used for queue.
public P2PMessage next = null;
public void setMessage(String sMessage){
this.sMessage = sMessage;
}
public String getMessage(){
return sMessage;
}
}
P2PMessageQueue.java
/**
* This Queue maintains the queue of messages coming from connected
clients.
*/
public class P2PMessageQueue {
private P2PMessage head = null;
private P2PMessage tail = null;
/**
* This method allows adding a message object to the existing
queue.
* @param oMessage
*/
public synchronized void enqueue(P2PMessage oMessage){
if (head == null) {
head = oMessage;
tail = head;
}
else {
P2PMessage temp = oMessage;
tail.next = temp;
tail = temp;
}
}
/**
* This method allows removing a message object from the existing
queue.
* @return
*/
public synchronized P2PMessage dequeue(){
if (head == null) {
return null;
}
else {
P2PMessage temp = head;
head = head.next;
return temp;
}
}
public boolean hasNodes(){
if (head != null)
return true;
return false;
}
}
P2PServer.java
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* This class is a separate thread that listens for messages from
connecting clients and adds messages to queue.
*/
public class P2PServer implements Runnable {
private int thisServerPort;
/**
* This constructor forces port to be passed in that is necessary
for startup of ServerSocket.
* @param iPort
*/
public P2PServer(int iPort){
thisServerPort = iPort;
}
/**
* This thread listens for connecting clients and receives messages
to add to Blockchain's transaction queue.
*/
public void run() {
BlockchainUtil u = new BlockchainUtil();
try (ServerSocket oServerSocket = new ServerSocket(thisServerPort))
{
System.out.println("Server is listening on port " + thisServerPort);
while(true) {
Socket oSocket = oServerSocket.accept();
u.p("[server]: New client connected: " +
oSocket.getRemoteSocketAddress());
InputStream input = oSocket.getInputStream();
BufferedReader reader = new BufferedReader(new
InputStreamReader(input));
OutputStream output = oSocket.getOutputStream();
PrintWriter writer = new PrintWriter(output, true);
// Get one time message from client.
String sReceivedMessage = reader.readLine();
u.p("[server]: Server received message: " + sReceivedMessage);
if (sReceivedMessage.startsWith("mined")) {
u.p("[server]: Block has been mined by other node.");
Miner.bAbortPoW = true;
writer.println("Server received: " + sReceivedMessage);
writer.flush();
}
else {
P2PMessage oMessage = new P2PMessage();
oMessage.setMessage(sReceivedMessage);
Miner.oIncomingMessageQueue.enqueue(oMessage);
writer.println("Server queued: " + sReceivedMessage);
writer.flush();
}
}
}
catch (IOException ex) {
u.p("[server]: Server exception: " + ex.getMessage());
ex.printStackTrace();
}
}
}
P2PUtil.java
import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
/**
* This class holds any utility methods needed for P2P networking
communication.
*/
public class P2PUtil {
/**
* Allows a one time socket call to a server, gets reply, and then
closes connection.
* @param sIP
* @param iPort
* @param sMessage
* @return
*/
public static String connectForOneMessage(String sIP, int iPort,
String sMessage){
try (Socket socket = new Socket()) {
// Connect to server and try up to 5 seconds
socket.connect(new InetSocketAddress(sIP, iPort), 5000);
OutputStream output = socket.getOutputStream();
PrintWriter writer = new PrintWriter(output, true);
// Send message
writer.println(sMessage);
writer.flush();
InputStream input = socket.getInputStream();
BufferedReader reader = new BufferedReader(new
InputStreamReader(input));
String returnString = reader.readLine();
socket.close();
return returnString;
} catch (IOException ex) {
System.out.println("[CLIENT]: Client exception: " +
ex.getMessage());
ex.printStackTrace();
}
return null;
}
}
output
Merkle root method for 2 items worked!
Merkle root method for 4 items worked!
PLEASE GIVEME THUMBUP...........