A Simple PoW Blockchain Implementation in Java(Part4)

By last blog, I have presented how to locally simulate a simple Proof-of-Work(PoW) Blockchain in Java with multithreading. In this blog, I will make proper modifications to implement a more complex Blockchain inspired by the idea of sharding.

Basic Idea of Sharding in Blockchain

Sharding is originally a concept popular in database area. It is the idea of dividing a huge chunk of data into several disjoint subsets, and each of the subset is called a shard. The reason recent researchers have been interested in applying sharding into Blockchain is that this concept seems to be helpful to solve the scalability problem of Blockchain technology. The mining congestion happens when the transactions producing rate is higher than the transactions processing rate of the Blockchain system, which causes longer and longer waiting time for the transactions confirmation as the market keeps growing. With the ideas of sharding and parallel mining, we can divide the overloaded transactions into smaller disjoint shards and allow multiple concurrently maintained sub-chains to record them. Ideally, in this way, we should be able to improve the transactions processing rate dramatically.

Sharding Data Structure in my Simulation

As there is a variety of different ways of implementing sharding in Blockchain, I will apply a relatively simple approach here. Let’s treat the Block data structure from previous code as Sub-Block, and redefine the Block as a collection of Sub-Blocks. The entire Blockchain data structure should look like the figure below:


Now every mining iteration, based on the transactions load, the Blockchain system would decide to divide next Block into a proper number of shards(sub-blocks), and evenly distribute compute powers to each of the shards. Miners of different shards will concurrently compute the satisfying nounce for the shard they belong to. For N shards, there will be N winners. Each of them will be able to generate a new sub-block and all the sub-blocks will be gathered together as a whole new Block to be attached to the Blockchain.

Implementation Modifications

First, the blockchain variable in Beecoin class should now be implemented as a ArrayList of Block Array:

private static ArrayList<Block[]> blockchain = new ArrayList<>();

Every mining iteration, we will first decide the number of shards we want, which is the length of the Block array, according to two things:
1. the transactions load
2. the number of miners

The reason we want to also consider the miners quantity is that we don’t want to have too many shards that there is not enough miners in charge of each shard. When there is only little miners for each shard, the safety of the shard is harmed. In my simulation, I simply assume that each shard will have at least two miners in charge of it.
We can compute the number of shards needed for each Block in this way:

desiredShards = (# of unconfirmed transactiosn) / (Capacity of each sub-block)
maxShards = (# of miners) / (minimum Miners per Shard)
IF desiredShards >= maxShards:
desiredShards = maxShards

Now, when we create a Miner thread for mining, we also want to tell it which shard it belongs to. Additionally, we need to modify the Miner class to let it support concurrent multi-blocks mining. The only things I need to change, is the class variables of Miner, which are used to support communication between Miner threads. They were designed to make sure all threads terminating when a solution is found by any thread. Now, they should make sure a thread only terminate when a solution is found by itself or a thread of the same shard. Thus, for N shards, there will be N final_nounce to be found. In other words, each of those class variables now should become an collection of what it was. Now the Miner class data members should look like this:

public class Miner extends Thread {
    public static boolean solutionClaimed[]; // modified
    public static int claimerID[]; // modified
    public static int candidate[]; // modified
    public static ArrayList consensusList = new ArrayList(); // modified
    public static int minerNum = 0;
    public static String final_nounce[]; // modified
    public static int shardsNum; // newly added
    public static int minersPerShard; // newly added

    private int difficulty;
    private long prevInfo;
    private int shardID; // newly added

    public int index;
    public String solution;
}

Results and Discussion

After all code modifications needed for the upgrade from a single-chain to a sharded-chain being done, we can finally test and compare the performance.

First, I set up the PoW difficulty level and the number of miners to be 4 for both single chain and sharded chain, which made the only difference to be the Blockchain data structure. I let both the single chain and the sharded chain systems to run for 15, 20, and 25 mining iterations, and I observed the transactions confirming rate of them. I got Figure-1 as followed:


In Figure-1, we can compare the transactions confirming rate between the classic single chain and the chain with sharded blocks. It not only shows the general better performance of the sharded chain but also shows that as more and more mining iterations pass, the single chain easily stucks at a confriming rate bottleneck, while the sharded chain clearly has a bigger growing space of transaction processing rate.

If we focus on the number of backlog transactions after 15, 20, and 25 mining iterations for both single chain and sharded chain system, we can see a pattern as followed:


In Figure-2, the advantage of sharded chain is more obvious. When the single chain has more and more serious mining congestion problem, the sharded chain shows a perfect transactions processing capability that eliminates the mining congestion problem when mining iterations being less than 25.
These two figures indicate a huge improvement of transactions processing throughput when update the single chain to a sharded chain.

What’s more, the sharded chain can have even better performance as it showed in Figures above. When the number of miners in the network grows, the transaction processing capability of the system grows with it.

I let the sharded chain system run for 15, 20, 25, 35, and 50 iterations with 4 miners and 6 miners. The difficulty level remained 4. It was possible to have at most 2 shards for the 4-miners system and 3 shards for the 6-miners system. Notice that as more and more iterations passed, the transactions load in my simulation tended to grow exponentially.


In Figure-3 and Figure-4, we can see clearly that the transactions confirming rate grows quicker with 6 miners. Also the number of backlog transactions grows much slower with 6 miners. This shows how dramatically the increase of miner numbers can improve the transactions processing capability of the system.

Conclusion

In this simulation, some assumptions have been made in order to focus on my main concern and avoid unnecessary complexity of the model. As the simulation successfully shows how sharding and parallel mining can dramatically improve the transactions processing capability, it gives us hope that we can apply sharding to the real world Blockchain to promote the transactions processing throughput to make it more realistic to apply Blockchain technology to more areas.

In the real world Blockchain systems with distributed network envolved, to be able to upgrade a single chain to a sharded chain, extra works are required on managing the communications among different shards. In the future, I will explore how to maintain a safe and efficient cross-shards communications so that not only the transactions processing throughput are enhanced but also the double-spending problem can be eliminated and the safety of the network is kept.

A Simple PoW Blockchain Implementation in Java(Part3)

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.