- 1.
Attaches new blocks to the blockchain
- 2.
Achieves distributed consensus on the content of the blockchain and hence distributed consensus on the transactions in the network
- 3.
Creates fresh units of cryptocurrency that are distributed to miners
In this chapter, we will implement the theoretical concepts pertaining to distributed consensus in the form of mining nodes that implement consensus by solving a difficult mathematical problem. Solving this problem is called proof of work, and it lets a mining node attach a block to the blockchain and hence collect a substantial mining reward as well as transaction fees. Mining is a computationally intensive task and hence consumes a substantial quantum of electricity. Nodes compete with each other to obtain the financial benefits of mining. The only way to maximize profit is to mine as many blocks as possible and attach them to the longest blockchain. Longer blockchains have more computational effort extended upon them. Miners will not attempt to attach newly mined blocks to a shorter secondary blockchain as this blockchain will eventually be deleted by other miners.
In this chapter, we will write the Python program code for a Helium mining node. As usual, we will buttress this code with pytest unit tests. So without further ado, let us commence this task.
Distributed Consensus Algorithm in Detail
Handling Incoming Transactions | ||
---|---|---|
A transaction arrives at a miners interface. There are two cases: | ||
Case A: | If the transaction is valid, add it to the mempool. In particular, since the transaction is valid, it does not spend transaction fragments in the Chainstate database which have already been spent. | |
Adding Transactions to a Candidate Block | ||
As transactions are added to a candidate block, we maintain a temporary list of prior transaction fragments that are going to be spent by these transactions. If a transaction selected for the candidate block tries to spend a fragment that is in this list, it is rejected for inclusion in the block and also deleted from the mempool. | ||
Case B: | The transaction is invalid. For example, the miner checks the Chainstate database and sees that this transaction references one or more previous transaction fragments that have already been spent. The transaction is rejected. | |
Handling Incoming Blocks | ||
A block arrives at a mining node interface: | ||
Get the block height of the incoming block. There are then five possible cases: | ||
Case A: | The block height is less than the height of the blockchain. | Reject the block; it is stale. A block at this height has already been mined. |
Case B: | The block height is greater than the height of the blockchain + 1. | Put the block in the orphan_blocks list. We will process this block later once our blockchain height increases. |
Case C: | The block height is equal to the height of the blockchain + 1. | We can add the block to the primary blockchain or the secondary blockchain (if it exists) depending upon the value of the prevblockhash attribute (see the following note). |
Case D: | The block height is equal to the current block height. | If the prevblock hash of the new block matches the block header hash of the parent of the block at the head of the primary blockchain, then fork the blockchain. Otherwise, reject the block (see the following note). |
Case E: | A block arrives which is greater than the blockchain height + 1. | Trigger a call to update our blockchain if a number of such blocks have been received by the miner. The blockchain is possibly behind the blockchain of other miners. |
Note: | Suppose that the head of our blockchain has a transaction that consumes a transaction fragment and the incoming block also has a transaction that consumes the same transaction fragment, then unless the miner that mined the incoming block is acting mala fides, the new block cannot have a prevblockhash value that points to the head of our primary blockchain. The new block must either sit on top of a secondary blockchain (if it exists) or have a prevblockhash that matches the block that is the parent of the head block at the head of our blockchain. In the latter case, we will have a fork of the primary blockchain. But in either case, two double-spending transactions cannot exist on one blockchain. One will exist on the primary blockchain, and the second will exist on the secondary blockchain. This solves the double-spend problem. Suppose further that we have a primary and secondary blockchain and a block arrives that has a prevblockhash value that matches the parent of the head on one of these blockchains. This block will be rejected, as it is in effect matching the block hash of the grandparent of the block at the head of the blockchain before it was forked (exercise: think this through with some diagrams). |
Helium Mining Code Walkthrough
Helium Mining Data Structures
mempool
mempool is a list containing transactions that have been received but are not in any mined block.
received_blocks
This is a list of blocks mined by other mining nodes and meant to be included in the receiving node’s blockchain.
orphan_blocks
This is a list of valid blocks which have been received from other nodes but which could not be appended to the receiving miner’s blockchain.
Blockchain
This is the local blockchain that is being maintained by this mining node. It is located in the hblockchain module. This blockchain is also referred to as the primary blockchain.
Secondary Blockchain
This is an alternate blockchain that is maintained by the miner when the primary blockchain is forked.
Helium Mining Functions
mining_reward
A miner who successfully mines a block is entitled to a reward which consists of the issuance of new helium coins. The mining reward determines the quantum of fresh currency awarded to the miner. This reward halves periodically. Both the initial reward and the halving period are set out in the hconfig module.
The initial reward is hconfig.conf["MINING_REWARD"] coins. This reward halves every hconfig.conf["REWARD_INTERVAL"] blocks.
receive_transaction
The function receive_transaction receives a transaction propagating on the Helium P2P network. This function checks to determine whether the transaction is in the mempool. The function discards the transaction if it is already in the mempool.
In the next step, receive_transaction verifies the validity of the transaction. If the transaction is valid, it is added to mempool; otherwise, the transaction is discarded. If the transaction is added to the mempool, it is sent for further propagation on the Helium network.
receive_transaction is meant to run in a Python thread.1
make_candidate_block
The purpose of make_candidate_block is to create a block that can be mined. If the mempool is empty, the function just returns.
make_candidate_block creates a partial header for a block and then draws transactions from the mempool and appends them to tx field of this block. Any number of algorithms can be used to draw transactions from the mempool. make_candidate_block uses a simple FIFO (First-In, First-Out) algorithm and keeps on drawing transactions until the maximum block size of 1 MB is almost reached or the mempool is exhausted. For each transaction, this function inserts a vout transaction fragment giving itself the transaction fee.
After make_candidate_block has finished its draw from mempool, it creates a coinbase transaction. The coinbase transaction is the first transaction in the tx list. For each block, this function creates a fresh public-private key pair that is used to create the coinbase transaction and the transaction fee fragment.
Finally, make_candidate_block computes the merkle root of the transactions and inserts it into the merkle field of the block header. It then tests the validity of the constructed block. If the block is valid, it removes the transactions in the block from the mempool. The candidate block is now ready to be mined.
make_miner_keys
This function creates a public-private key pair and saves these keys to a text file. make_candidate_block uses the public key to create the RIPEMD160(SHA256(public key)) hash that is inserted into the ScriptPubKey portion of the coinbase transaction and each transaction fee vout fragment.
add_transaction_fee
add_transaction_fee is called from make_candidate_block. It receives a transaction and the miner’s public key. This function calculates the transaction fee, updates the Chainstate, and creates a transaction fragment that directs the transaction fee to the holder of the private key corresponding to the public key.
make_coinbase_transaction
make_coinbase_transaction is called from make_candidate_block. It receives the height of the candidate block and the miner’s public key. This function computes the block reward and then creates and returns a coinbase transaction.
remove_mempool_transactions
This function removes transactions from the mempool and hence prevents transactions from being included more than once in different candidate blocks.
mine_block
mine_block receives a candidate block and initiates mining this block. The SHA256 hash of the nonce in the block header is computed. The block is said to be mined if the computed SHA-256 hash is less than the difficulty number in the Helium hconfig module. If the value is greater than or equal to the difficulty number, then the nonce is incremented and the computational result is compared once again to the difficulty number. This process continues until the block is mined or the computational loop is exited.
Once the block is mined, it is attached to the miner’s blockchain and then propagated on the Helium P2P network so that other miners can append it to their blockchains.
proof_of_work
Before a block received from another miner can be included in a local blockchain, the node must verify the integrity of the block and its transactions and also verify that the nonce supplied in the block header is less than the difficulty number in this node’s hconfig file. This function implements the proof of work for the received block.
receive_block
- 1.
The block is invalid.
- 2.
The block height is less than (blockchain height – 2).
- 3.
The proof of work fails.
- 4.
The block does not contain at least two transactions.
- 5.
The first block transaction is not a coinbase transaction.
- 6.
Transactions in the block are invalid.
- 7.
Block is already in the blockchain.
- 8.
The block is already in the received_blocks list.
If the received block is valid, it is added to the received_blocks list and the block is propagated on the Helium P2P network so that other nodes can attach it to their blockchains.
receive_block is meant to be executed as a Python thread.
process_received_blocks
- 1.
Get a block from the received blocks list.
- 2.
If the blockchain is empty and the block has an empty prevblockhash field, then add the block to the blockchain (this is a genesis block).
- 3.
Compute the block header hash of the last block on the blockchain (blkhash)
- 4.
If the blockchain has at least two blocks, let blk_hash be the hash of the block header of the second latest block in the blockchain, and then if
- 5.
Otherwise, move the block to the orphan_blocks list.
- 6.
If the received block was attached to a blockchain, then for each block in the orphan_blocks list, try to attach the orphan block to the primary blockchain or the secondary blockchain, if it exists.
- 7.
If the received block was attached to a blockchain and the secondary blockchain has elements, then swap the primary blockchain and the secondary blockchain if the length of the secondary blockchain is greater.
- 8.
If the received block was attached and the secondary blockchain has elements, then clear the secondary blockchain if its length is more than two blocks behind the primary blockchain.
process_receive_block is meant to run in a concurrent thread.
fork_blockchain
This function creates the secondary blockchain by forking the primary blockchain. fork_blockchain receives a block parameter which it attaches to the secondary blockchain.
swap_blockchains
swap_blockchains swaps the primary and secondary blockchains so that the primary blockchain is the longer blockchain.
handle_orphans
handle_orphans draws orphaned blocks from the orphans list and attempts to attach them to the primary blockchain. Orphaned blocks that are more than two blocks distant from the head of the blockchain are deleted.
remove_received_block
This function removes a block from the received_blocks list.
retarget_difficulty_number
This function is invoked every hconfig.conf[“RETARGET_INTERVAL”] number of blocks. It recalibrates the difficulty number. The purpose of this function is to ensure that blocks are mined every ten minutes on the average. This function implements the algorithm set out in Chapter 13.
Miscellany
The remaining functions in the hmining module handle concurrent mining and will be discussed in the next chapter.
Changes to the Helium Blockchain Module
Helium Mining Unit Tests
Conclusion
At this stage, we have implemented a substantial portion of a Helium mining node. But our implementation of a mining node is not over. We have yet to implement portions of the hmining module as concurrent processes and interface the mining node with the external Helium peer-to-peer network. This is the matter that will be handled next.