DEV Community

CoinMonks
CoinMonks

Posted on • Originally published at Medium on

Building a blockchain simulator

Github code here!

Motivation

I’ve become quite interested in all things crypto in the last couple of weeks. A bit late to the party, but I hope not all that late. I wanted to get a deeper understanding of how things work so I started reading on the primordial cryptocoin — Bitcoin.

Then I decided to reproduce at least some of the main functionalities of the Bitcoin blockchain, but with a more interactive twist that I’ve yet to see in all the tutorials I went through. The idea was to have a view of the network and its actors doing their work, all on my monitor screen in front of me.

And for this task I chose Java.

Documentation

You see, there is no logical reason for choosing Java, it’s just by preference. And luck has it that other people used it in similar fashion. As I searched for other sources of learning, I found CryptoKass’s Blockchain Dev Megabundle which has plenty resources to get any newbie blockchain developer up to speed. One of his tutorials even has a blockchain implementation written in Java which helped along the way and from which I borrowed some code.

Alongside the megabundle was the Bitcoin whitepaper written Satoshi Nakamoto, which I found very nicely explained.

Description of the simulator

My end product was supposed to be literally a toy blockchain simulator, where miners and users could join and do the basic actions people do on a Proof of Work consensus blockchain like Bitcoin — mine and trade coin. All of this would happen locally, on one PC, with each network participant represented by a different instance of the program, through the terminal. Thus came the idea for the naming scheme— LocalChain and its native token, LocalCoin.

Through most of development I tried to either follow the original Bitcoin paper or improvise where such an implementation was impossible. To create the illusion of a decentralised network, I used a SQLite database in which there is stored everything that should be broadcasted from one user to the rest of the network.

Where each notebook shown above is actually a different process on the same computer

What does that even mean? It means that the ‘network’ itself lives on the database mentioned above. From a node’s point of view, things kind of look like a proper decentralised blockchain. The node either mines a block or sends some LocalCoins to another wallet through the appropiate API’s, so both these actions end up with the node broadcasting data to the network. As soon as the node sends its data to the network, it doesn’t really care what happens after that point.

It is true that this implementation has its errors and it is definitely far from the whitepaper Satoshi proposed, but my purpose was not to recreate Bitcoin, but to understand its inner technical workings and implement some sort of an interactive simulation. In trying to achieve that, I realised the networking part would be cumbersome so I took this route using a database.

The result of this implementation is that all nodes are being served the same data — the same blockchain and the same transaction pool — in what could be viewed as a very fast network that propagates data to all nodes so that there can never be two machines with different data broadcasting at the same time and splitting the network in two parts (essentially forking the chain). So, in the end… it’s really just like a centralised authority.

All the miners on the ‘network’ will solve new blocks and try to push their version of the chain towards everybody else (aka saving it to the DB). Another deviation from the Bitcoin standards is that before a miner pushes his version, he checks with ‘the network’ (aka the DB) whether his chain is the longest. If it is, then he pushes it to the DB and all other nodes start using that one. Otherwise, he drops that version, picks up the correct one from the DB and starts using it to solve new blocks. To clarify, in this implementation, the longest chain is, in fact, the longest chain by size and not by effort put into it. So if a complicated four-block chain with difficulty six exists, it will be considered invalid in front of a five-block chain with difficulty 1, with the difficulty being the standard proposed, meaning the number of zeroes that prefix a hash solution in order for that hash to be considered valid for a certain block.

It is clear by now that the network side is far from the original proposal, so many issues like hard forks or 51% attacks cannot be reproduced in this blockchain simulator. On top of everything noted above also comes the fact that each miner has literally the same processing power, each being a process running on the same computer, which means little competition between the miners: the first one on the network will always have the longest chain and will produce all consequent blocks, since computing power is constant across all miners.

How does it actually work?

The simulator works by running multiple instances of the compiled program. The very first run will create a process that will setup the ‘network’ by creating a local database stored in a file called ‘localchain.db’. This is the main functionality of this first process and after it finishes setting everything up, it will remain alive in the terminal to print the blockchain data, whenever the user asks for it.

The way the program splits and follows different functionalities is defined in the ‘Main’ class. Since the DB is kept as a local file, the program will check if it exists and create it if it doesn’t. Afterwards, it will wait for the user to say what he wants that particular program instance to be — a miner or a trader.

File f = new File(System._getProperty_("user.dir")+"/localchain.db");

if (!f.exists()) {
    System._out_.println("Starting the network!");
    network = new Network();
    network.startNetwork();

    // ask the user if he wants to print the currently accepted blockchain

    while (answer != null) {
        //print the blockchain

    }
} else {

    ...

    // ask the user if he wants to launch a miner or a trader

    switch (answer) {
        case 1:
            System._out_.println("Joining network as a miner!");
            Miner miner = new Miner(userName);
            miner.startMining();
            break;
        case 2:
            System._out_.println("Joining network as a normal trading user!");
            User trader = new User(userName);


            //logic that sends money from the network wallet to the first trader to get on the blockchain
            ...

            trader.startTrading(userInput);
            break;
    }
}
Enter fullscreen mode Exit fullscreen mode

The ‘Network’ class seen above is the class that prepares the DB by creating the needed tables and creating a genesis block. Since we have no users on the blockchain at the time of its creation, the DB will also store a pair of public/private keys which will represent the network’s wallet. This wallet will make 1000LC’s out of thin air and send them to the first trader that gets on the network. This will be considered the genesis transaction.

This class also represents the interface through which other objects get to insert, select, update or delete data from the DB (aka the network).

Also to be noted is the ‘while()’ instruction from above. All the processes are stuck in an infinite loop as to make this work. The first process is the most obvious since the instruction is right there in the main file, but both the users and the miners processes also loop infinitely in their respective methods: ‘startTrading()’ and ‘startMining()’.

Blocks, miners and traders

The blocks have been implemented in the standard way, their properties being the hash, previous block hash, nonce, timestamp and a Merkle tree root representing the transactions added.

The nonce, or the difficulty of the block, raises based on the number of miners on the network using, yet again, the database to keep the number of participants recorded.

Miners will constantly mine blocks and create new ones.

while(true) {
    networkChain = Network._getLatestChainFromFakeNetwork_();
    if (localChain.size() < networkChain.size()) {
        localChain = networkChain;
    }

    Network._adaptDifficulty_();

    Block lastBlock = Network._getLastBlockInChain_();
    System._out_.println(lastBlock.toString());
    if (!lastBlock.isMined()) {
        Block solvedBlock = pickaxe.mine(lastBlock, Network._difficulty_);

        localChain.get(localChain.size() - 1).transactionIds.forEach(Network::_confirmTxRecord_);
        localChain.get(localChain.size() - 1).transactionIds = null;
        localChain.get(localChain.size() - 1).nonce = solvedBlock.nonce;
        localChain.get(localChain.size() - 1).hash = solvedBlock.hash;

        Block block = new Block(localChain.get(localChain.size() - 1).hash);
        ArrayList<TxRecord> txRecords = Network._getUnconfirmedTxRecords_();
        if (txRecords != null) {
            txRecords.forEach(block::addTx);
        }
        block.merkleRoot = CryptoService._getMerkleRoot_(block.transactions);
        //transactions need not stay on block. only merkleRoot
        block.transactions = null;

        localChain.add(block);

        networkChain = Network._getLatestChainFromFakeNetwork_();
        if (localChain.size() < networkChain.size()) {
            localChain = networkChain;
            System._out_.println("My chain is smaller than network chain, switching to that one...\n\n");
        } else {
            Network._sendChainOverFakeNetwork_(localChain);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

First, a miner will check if they are building upon the accepted chain and adapt the difficulty, then it will mine the last block. The mining algorithm is placed in the ‘MineUtils’ class and uses code from CryptoKass. Actually, everything that implies anything cryptographic is code written by CryptoKass, so, for those looking through the code, the entire ‘CryptoService’ class.

The next thing a miner does is build a new block. To do that, it will get transactions from a pool of unconfirmed transactions, build a Merkle tree out of them and calculate the Merkle tree root. The other properties are also set on the block, the block is added to the chain, and then the miner broadcasts the entire chain to the network.

Another drawback of the current implementation is that transactions are considered confirmed not after the block is mined but as soon as the miner adds the transaction to the block. This has not been addressed since, as said earlier, the computing power of each miner is constant, so the first miner that joins the network will be the one to always create a new block and mine it shortly after.

On the other side are the traders. The traders are mostly represented by their wallets, and can send or receive LC’s. The console will print each trader’s public key and balance, and that information can be used to send coins. It’s easy to send funds from one user to another since there are easy to follow instructions on the terminal screen.

From an implementation perspective, the user pretty much does this:

while (true) {
    System._out_.println("Your wallet ID is: " + CryptoService._getStringFromKey_(this.wallet.publicKey));
    System._out_.println("Balance: " + this.wallet.getBalance() + " LocalCoin");
    System._out_.println("Do you wanna trade? (y/n)");
    String tradeSomething = userInput.next();
    userInput.nextLine();

    if (tradeSomething.equals("y")) {
        System._out_.println("\nWho do you wanna send LC to?");
        String publicKeyIdentifier = userInput.nextLine();

        System._out_.println("\nWhat amount of LC to send?");
        float val = userInput.nextFloat();
        userInput.nextLine();

        if (this.wallet.sendFunds(
CryptoService._getPublicKeyFromString_(publicKeyIdentifier),val)) {
            System._out_.println(this.name + " has sent funds to wallet ID " + publicKeyIdentifier);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Transactions follow the Bitcoin whitepaper implementation, which says that a transaction is formed out of input transactions and output transactions. This way, the coins can be traced to an origin point and keep track of one user’s balance so he doesn’t send someone coins he doesn’t own. More specifically, if a user wants to send coins, then there must exist one or more transaction records in which he receives at least the amount of coins he wants to send, and this goes on and on.

Example transaction records

In the image above, let’s assume that the 0.5BTC go from the ‘Out’ field of the first transaction into the ‘In’ field of the second. It is thanks to those ‘In’ fields that the second transaction can exist, and some user can send the 0.8BTC. But what about that 49.5BTC ‘Out’ field? The Bitcoin paper explains that for every transaction, the sum of the ‘In’ fields has to be equal to the sum of the ‘Out’ fields. So in such a case, if the user does not want to empty his wallet, he can simply send the remainder coins back to himself, in which case, the transaction sends the coins to the same address that they leave from.

Usually, in a blockchain, when a user wants to send coins to another, he will generate this new transaction, set the ‘In’ and ‘Out’ fields, sign the transaction using his private key and then broadcast it to the network. This record will then be picked up and verified by other nodes. They will decide if the transaction is valid or not, by checking the signature and the ins and outs, and send it to the pool of unconfirmed transactions from which the miners will choose.

Alas, this is not how the simulator works. A simple balance verification is done locally, by the user, before he sends the funds. He verifies that he has enough unspent transaction outputs (UTXO’s) and then creates the transaction, signs it, sets who receives what amount and broadcasts it to the network. Later, when a new block is being created and the miner adds the transaction to it, he verifies the signature and drops the transaction if invalid.

public boolean sendFunds(PublicKey _recipient, float value ) {
    if(getBalance() < value) {
        System._out_.println("Not enough funds to send transaction. Transaction Discarded.");
        return false;
    }
    ArrayList<TxInput> inputs = new ArrayList<>();

    float total = 0;
    for (Map.Entry<String, TxOutput> item: UTXOs.entrySet()){
        TxOutput UTXO = item.getValue();
        total += UTXO.value;
        inputs.add(new TxInput(UTXO.id));
        if(total > value) break;
    }

    TxRecord newTransaction = new TxRecord(publicKey, _recipient , value, inputs);
    newTransaction.generateSignature(privateKey);
    Network._addUncofirmedTxRecord_(newTransaction);

    for(TxInput input: inputs){
        UTXOs.remove(input.txOutputId);
    }

    return true;
}
Enter fullscreen mode Exit fullscreen mode

I mentioned above about miners choosing what unconfirmed transactions get confirmed into a block. Normally, each transaction should have a fee attached to it. This fee would be for the miner to collect, alongside another reward for finishing the block and adding it to the chain. This is what keeps miners motivated in a real life scenario. Users declare how much of a fee they want to send away to the miners, which brings up another issue: if a user pays too little in fees, no miner will choose their transaction and it will end up being ignored.

Such problems do not exist in the presented simulator. There are no rewards for the miners, they do the job out of the goodness of their hearts. They also do not get to choose what transactions go into the blocks, since this simulator doesn’t work with millions of transactions a month, like Bitcoin does. All transactions are being processed as they reach the pool.

Conclusion

Even though the presented solution has no real life utility and differs from the original Bitcoin implementation, it provides an interactive way to better visualise blockchains. It also retains most of the implementations discussed in the original Bitcoin paper and has helped me better understand how blockchains work.

I hope that it will prove helpful for other cryptocurrency enthusiasts who are curious on how things work under the hood.

Example functionality of the program

Join Coinmonks Telegram group and learn about crypto trading and investing

Also, Read

Get Best Software Deals Directly In Your Inbox


Top comments (0)