Project 3: Parallel Cryptocurrency Miner (v 1.0)
Starter repository on GitHub: https://classroom.github.com/a/IrH8JWxj
After infiltrating top spy agencies using your distributed password cracking utility, you retrieve a cache of classified information. This information is highly valuable and should fetch a nice sum on the black market, but there is one problem: simply depositing your ill-gotten gains in a bank account will almost certainly be traced back to you and seized by the authorities. Since using offshore shell corporations to launder money is soooo outdated, you decide to create your very own cryptocurrency instead.
As luck would have it, such a task is quite amenable to parallelization. Cryptocurrencies like Bitcoin rely on a distributed transaction ledger composed of blocks. The job of miners in the Bitcoin network is to verify transactions within these blocks, which is computationally expensive.
In this assignment, you will get more familiar with:
- The pthread library and parallelization using threads
- The producer/consumer paradigm
- Taking performance measurements
You are given a complete sequential program and must parallelize it using the pthread library. Because of the inherent randomness of cryptocurrency mining, using more threads will improve the probability of finding a solution in a shorter amount of time.
Bitcoin (and other cryptocurrencies) are based on Hashcash. The idea behind these systems is called proof-of-work, which is somewhat like a CAPTCHA designed for computers instead of humans. The computer works on a computationally expensive problem to prove its actions are legitimate; in the case of Hashcash, performing the computation proves that you are not a spammer, while in Bitcoin it serves to verify transactions as being valid. A key feature of proof-of-work systems is that once the solution to the problem is found, it is trivial to verify that the answer is correct.
In Bitcoin, hash inversions are the computationally expensive problem being solved by the computer. Given a block, the algorithm tries to find a nonce (number only used once) that when combined with the block data produces a hash code with a set amount of leading zeros. The more leading zeros requested, the harder the problem is to solve. It’s kind of like rolling dice; rolling any number from 1 to 3 is much easier than rolling a 6. This assignment searches for the nonce that satisfies a given difficulty level by splitting the work across multiple cores (or machines).
After successfully performing the hash inversion, the Bitcoin network rewards the miner (or pool of miners) for their work. With our cryptocurrency, a working program will reward you with a good grade :-).
Here’s a demo run for the completed, parallel version of the program:
./mine 4 24 'Hello CS 220!!!' Difficulty Mask: 00000000000000000000000011111111 Number of threads: 4 Solution found by thread 1: Nonce: 1011686 Hash: 000000B976A3E2B94CB9AB668E0C9C727782787B 1016000 hashes in 0.26s (3960056.52 hashes/sec)
In this example, 4 threads are used to find the solution to the block: the nonce that satisfies the given difficulty (24 zeros in this case). The process behind finding a solution works like this:
- Program starts, block data is provided by the user:
Hello CS 220!!!
- Each thread receives a task to work on. A task is simply an array of nonces.
- The thread begins appending each nonce in the task array to the block data and then hashing the resulting string. For example, the first combination will be
Hello CS 220!!!0, followed by
Hello CS 220!!!1, and so on.
- Once a thread finds a hash that begins with the correct amount of zeros (specified by the difficulty mask), the process is complete.
In our implementation, the main thread produces tasks, and each worker thread performs the hash inversions. If the user specifies
4 for the thread count, this means you’ll have five total threads (main thread + 4 workers).
Testing Your Code
The great thing about cryptocurrencies is their proof-of-work paradigm: it takes a long time to produce a solution for a given block, but verifying that the solution is correct is relatively trivial. We can even do this on the UNIX command line. Let’s assume your block data was
'Hello CS 220!!!' and the nonce of the solution you found was
1011686 with a difficulty level of 24. We can test this with the
sha1sum utility. If you’re a Mac user, you can install the
coreutils package to get it (then run
gsha1sum1 INSTEAD of just
sha1sum). Otherwise, use a Linux machine.
echo -n 'Hello CS 220!!!1011686' | sha1sum 000000b976a3e2b94cb9ab668e0c9c727782787b
Note that the resulting hash has 6 zeros, which is what we’d expect: 24 bit difficulty means 6 hex characters worth of zeros (24 / 4 = 6).
The grade breakdown for this assignment is:
- 2pts Difficulty mask calculation
- 4pts Thread creation
- 4pts Correct use of condition variables
- 4pts Shutting down threads when a solution is found
- 4pts Final statistics and initial printouts (threads, timing, hashes/sec, etc.)
- 2pts Proper command line argument handling and error checking.
- 3pts Function documentation and comments
- 2pts Code style (no commented out blocks of code, unused variables, inconsistent indentation)
- 5pts Performance questions (edit README.md provided in the starter repository).
- First version posted (4/22).