In the last two blogs, I have introduced how I gradually implemented most of the properties of a typical ProofofWork(PoW) Blockchain including the simulation for transactions generating, transactions processing, and the PoW algorithm. However, I made an assumption that no mining competition really happened, in other words, I just randomly picked one miner to be the winner. In this blog, in order to make the consensus mechanism more realistic, I will introduce how to simulate the mining competition between multiple miners.
Multiple Miners Simulation via Multithreading
The basic idea is to implement competing miners as concurrent mining threads. Therefore, I created a Miner class and let it extend the Thread class which provides multithreading properties. Additionally, I used a couple of class variables(static variables) to support communication between threads.
class Miner extends Thread {
public static boolean solutionClaimed = false;
public static int claimerID = -1;
public static int candidate = Integer.MIN_VALUE;
public static ArrayList<Boolean> consensusList = new ArrayList<Boolean>();
public static int minerNum = 0;
public static String final_nounce;
private int difficulty;
private long prevInfo;
public int index;
public String solution;
public Miner(String minerID, long prevInfo, int difficulty) {
super(minerID);
index = minerNum;
minerNum++;
consensusList.add(false);
solution = "";
this.prevInfo = prevInfo;
this.difficulty = difficulty;
//System.out.println("Creating miner thread: " + minerID);
}
public void run() {
// TODO: Proof-of-Work
}
...
}
Variables
The static integer variable minerNum is for tracking the total number of constructed miner threads, and it is used to initiate the non-static variable index which identifies each thread among all. The static boolean arraylist consensusList has the length of minerNum, and each element of it indicates an individual miner’s attitude to the PoW solution provided by certain miner who claims to solved the PoW. The static integer variable claimerID indicates which miner claims that he/she solves the PoW and the static integer variable candidate is the integer provided by the claiming miner that sha256(candidate+previousBlock.timestamp) contains the required number of leading zeros. The static String variable final_nounce is generated by concatenating verified candidate value and the timestamp of previous block.
The non-static variable difficulty and prevInfo are passed to the miner thread while constructing and are used to compute the satisfying nounce.
Methods
To achieve concurrent mining among multiple miners(threads), I overrided the void run() method with the PoW algorithm:
@Override
public void run() {
// System.out.println("Running miner thread: " + this.getName());
int nounce = Integer.MIN_VALUE;
while (!consensusAchieved()) {
while (!solutionClaimed && !numLeading0is(difficulty, Encryption.sha256("" + nounce + prevInfo))) {
nounce++;
if (nounce == Integer.MAX_VALUE
&& !numLeading0is(difficulty, Encryption.sha256("" + nounce + prevInfo))) {
prevInfo++;
nounce = Integer.MIN_VALUE;
}
}
if (solutionClaimed) {
// if someone else claims that a solution is found, verify that
if (numLeading0is(difficulty, Encryption.sha256("" + candidate + prevInfo))) {
consensusList.set(index, true);
} else {
// if this candidate fails the verification
resetConsensus();
}
} else if (numLeading0is(difficulty, Encryption.sha256("" + nounce + prevInfo))) {
// if this miner finds a solution, report to the public, and wait for
// verification
solutionClaimed = true;
consensusList.set(index, true);
candidate = nounce;
claimerID = index;
}
}
final_nounce = "" + candidate + prevInfo;
System.out.println("Miner" + (this.index + 1) + "(" + this.getName() + ")" + " has approved that Miner"
+ (claimerID + 1) + " came up with the correct solution: " + "\"" + final_nounce + "\"");
}
So a Miner thread will continuously look for the satisfying nounce until it find it or any other miner thread claims to find it. When a Miner thread claims to find a satisfying nounce, it will set the static boolean variable solutionClaimed to be true in order to inform other concurrent threads to stop trying temporarily and verify the value it just put into the static int variable candidate. If a Miner thread of index i approves of the candidate value, it will set consensusList[i] to be true. If all Miner threads agreed that the candidate is satisfying, then they will all terminate and then the consensus is achieved. So the following method determines whether the consensus is achieved:
private boolean consensusAchieved() {
boolean agree = true;
for (int i = 0; i < consensusList.size(); i++) {
if (consensusList.get(i) == false) {
agree = false;
break;
}
}
return agree;
}
Notice that whenever a Miner finds that the the candidate doesn’t satisfiy the requirement for the nounce, all verifying work will stop and the current claim of solution will be ignored and all threads will continue looking for the correct nounce. Thus, we need a method to reset all static variables to the default value. Also, every time a new Block is about to be mined, a new competition of finding the nounce happens. So before each iteration of Block mining, we need to reset the static variables too.
...
// called from inside the class Miner
private void resetConsensus() {
// 1, reset the consensusList to all false
for (int i = 0; i < consensusList.size(); i++) {
consensusList.set(i, false);
}
// 2, reset candidate
candidate = Integer.MIN_VALUE;
// 3, reset solutionClaimed to false
solutionClaimed = false;
// 4, reset claimerID to -1
claimerID = -1;
}
...
// called from outside the class Miner
public static void reset() {
solutionClaimed = false;
claimerID = -1;
candidate = Integer.MIN_VALUE;
for (int i = 0; i < consensusList.size(); i++) {
consensusList.removeAll(consensusList);
}
minerNum = 0;
final_nounce = "";
}
...
Besides creating this Miner class, I also modified the Beecoin class, specifically, the mine() method. This is neccessary since now the winner miner is not randomly picked from all miners but is approved by all other miners.
The modified version of mine() method:
public static Block mine() {
Block lastBlock = blockchain.get(blockchain.size() - 1);
//String miner_address = getMinerAddr();
Miner.reset();
for (int i = 0; i < miners_address.size(); i++) {
Miner mt = new Miner(miners_address.get(i), lastBlock.getTimestamp(), DIFFICULTY);
miner_threads.add(mt);
mt.start();
}
for (int i = 0; i < miner_threads.size(); i++) {
try {
miner_threads.get(i).join();
} catch (InterruptedException e) {
System.out.println("Thread interrupted.");
}
}
System.out.println();
miner_threads.removeAll(miner_threads);
//String nounce;
//nounce = proofOFwork(lastBlock.getTimestamp());
Block next = createNextBlock(lastBlock, Miner.final_nounce);
Transaction nextToBeConfirmed[] = new Transaction[Block.BLOCK_SIZE];
String miner_address = miners_address.get(Miner.claimerID);
// rewards to the miner will be the first txion
nextToBeConfirmed[0] = new Transaction("System", miner_address, MINING_REWARDS, true);
retreiveVerifiedTxions(nextToBeConfirmed);
next.setTransactions(nextToBeConfirmed);
next.setNote("This is Block #" + next.getIndex());
next.setHash();
return next;
}
The main difference, is that I used two for loops to start all miner threads and join all of them. Now when we create a new Block, we don’t pass in return value of proofOFwork() method in Beecoin class, and instead, we pass in the static variable final_nounce of Miner class. And the winner miner’s address will be retrieved according to the static variable claimerID of Miner class.
Test
I let the miner thread print some message when it verifies a solution, and I run the whole Beecoin project I get following result:
Miner1(617b672120aa914054db960299fc1fa8c229e8b31ed596c651cfad09d04d336e) has approved that Miner2 came up with the correct solution: "-21473401071543985328663"
Miner3(a3cc07dee0ea2341a89e117eb12cbbed3739912c8dde4704649c5b73c570d996) has approved that Miner2 came up with the correct solution: "-21473401071543985328663"
Miner2(ada68893c9c6fa324307c3964f1eb6d871253665b0d0173168032da8a7b8a2fd) has approved that Miner2 came up with the correct solution: "-21473401071543985328663"
Miner4(75ec216e32856f59cf033dd89fbb1bcd570506c3ccce1a2c2d9e2ebcdcdbd6ac) has approved that Miner2 came up with the correct solution: "-21473401071543985328663"
Miner4(75ec216e32856f59cf033dd89fbb1bcd570506c3ccce1a2c2d9e2ebcdcdbd6ac) has approved that Miner1 came up with the correct solution: "-21474082291543985329102"
Miner2(ada68893c9c6fa324307c3964f1eb6d871253665b0d0173168032da8a7b8a2fd) has approved that Miner1 came up with the correct solution: "-21474082291543985329102"
Miner1(617b672120aa914054db960299fc1fa8c229e8b31ed596c651cfad09d04d336e) has approved that Miner1 came up with the correct solution: "-21474082291543985329102"
Miner3(a3cc07dee0ea2341a89e117eb12cbbed3739912c8dde4704649c5b73c570d996) has approved that Miner1 came up with the correct solution: "-21474082291543985329102"
...
The output above shows the mining competition before first two blocks are mined. Miner2 won the first competition and Miner1 won the second. All miners approved their solution too.
Discussion
In this blog I present how I simulate mining competition with multiple threads and now the simulation has become more complete. Next time I will try to implement the idea of sharding by maintaining multiple chains with multithreading.