Lab 5: Proof of work
This week we will add real mining to the blockchain to slow things down and make it more stable and reliable.
Goatchain Version 1
We are making a “hard fork” of goatchain from version 0 to version 1.
The most important change is a proof of work: the hash of any valid version 1 block must start with 24 binary zeros. (To check this from the hex string, it might be helpful to remember that each hex digit corresponds to 4 binary digits.)
Recall that, so far, a block contains three fields: prev_hash
, payload
, and version
. Now we will get an additional field nonce
, which must be an integer. The reason we need a nonce is so that you will be able to find a node satisfying the proof of work without having to randomly change the actual block contents.
So the block requirements for a valid version 1 block are:
- Total size of JSON-encoded block string must be at most 1024 bytes
- Hash of the block string must start with 24 zeros in binary. (Conveniently, this corresponds to 6 zeros in hex.)
prev_hash
: same as before. Must be an empty string, or the hash of a (valid) previous block. Note that the previous block is allowed to be version 0 or version 1.payload
: same as before. Must be a dictionary. If it has achat
field, the value for that field must be a string.version
: must equal 1 (of course!)nonce
: can be any integer
In addition to these validity requirements, we also change the definition of the “longest chain”: only version 1 blocks count towards the longest chain. What that means in practice is that adding new version 0 blocks (without doing the hard work of mining) won’t work on a version-1 server.
Part 1: Server upgrade
Upgrade your server.py
, running on port 5002, to version 1.
Notes:
- You should validate version 0 blocks (same as before) AND version 1 blocks (new rules above)
- As before, you should accept any valid block with a
/push
request as long as you have the previous block cached, but yourhead
should only update when the longest chain increases. And remember, only version 1 blocks count towards the chain size now. - Your handlers for
/head
and/fetch
probably don’t need to change at all!
The cat
server, port 5002, is pre-loaded with a few valid version 1 blocks that you can fetch
and use for testing if you want.
Part 2: Mining a new block
Do this first: add cat
to the list of hosts in hosts.txt
. (That is your instructor’s server which is starting out with a couple valid version-1 blocks on it.)
Upgrade your send_chat.py
from last week’s lab so that it mines and transmits a new version 1 block.
Your code will need to try many, many nonces in the block, hash the block, see if the hash starts with enough zeros, then repeat and repeat until it works. Then send it out and hope no one else beat you to it!
Hints:
- Try the nonces sequentially, like starting from some number you choose and then counting up.
- 24 binary zeros means your code will need to try (on average) about 16 million random nonces before it finds one that works. That’s a lot! On my unoptimized solution, it takes about 5 minutes to find a new block. So, while you’re debugging, set the difficulty lower to like 16 binary zeros (which should only take a few seconds to find). This won’t successfully push to any other nodes, but you can at least make sure the hash is working!
Specific assignment: Mine a block which makes it onto the “main” consensus blockchain, with a message that is unique and special which you can enter on the Google form.
OPTIONAL Part 3: Don’t waste time
When the blockchain gets going across the class, we will soon have many people trying to mine the same block at once. This can lead to wasted effort: if you start trying to make a new block (which takes about 5 minutes), and about 1 minute into it someone else manages to mine the next block, now your program is not going to add to the longest chain!
So, modify the logic in send_chat.py
so that it periodically interrupts the mining to check for a new longest chain - maybe every 100,000 hashes or so.
OPTIONAL Part 4: Mine faster!
Obviously, it would be great if it didn’t take so long to mine each new block. Here are some things you can try to speed it up:
- Use multi-threading (Note, I don’t recommend running more than 2 threads on your VM, unless you take special precautions so it doesn’t bog down the entire system and force you to reboot.)
- You can’t reasonably cut down on the time for computing hashes, but you can potentially cut down on the other time in your loop, like encoding the block to JSON with each updated nonce… try to eliminate any unnecessary steps in your loop!
- Write the mining program in a faster language like Java, C++, or Rust. This will require some extra work, but it might “pay off” for you! Ask your instructor if you’d like some guidance.