Today I’ll demonstrate how to make a simple proof of work blockchain in Python in less than 100 lines of code.
Setting the initial conditions
First, let’s set the network difficulty. This will adjust how hard it is to mine a block. In real life, the network adjusts the difficulty dynamically to force the blocks to be discovered at a fixed, predictable rate.
difficulty="0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"

Next, let’s draft a transaction to put in the first block. I’m using email addresses instead of network addresses in this example. I’m calling the coinbase by it’s namesake. I’ve also included a version stamp, which I am calling “1”.
transactions = [
{
"version":1,
"inputs":{"coinbase":50},
"outputs":{"my_email@yahoo.com":50}
}
]
Next, let’s define the previous block. This block contains basically nothing, since it should not exist.
The hash of the block is defined as being the zero hash, and it has a timestamp of “right now”. I’ll encode the difficulty in the block too.
block = Block(
prev_block="0x"+"0"*64,
timestamp=int(time.time()),
transactions=transactions,
difficulty=difficulty
)
Now, let’s try mining a block. The magic comes from the “Block” class at the end of this article. I’ll use the simple version for now.

First, let’s set the initial conditions. The nonce is the number we’ll use to affect the output such that it falls within the network difficulty.
nonce = 0
is_valid = False
print("\nMining...")
begin = time.time()
Let’s iterate through different nonces until we find a block.
while not is_valid:
block.set_nonce(nonce)
is_valid = block.is_valid()
nonce += 1
if not nonce % 100000:
block.set_timestamp(int(time.time()))
Once this loop completes, we will have found our block!
print("Found valid block in {0:0.1f} minutes: {1}".format((time.time()-begin)/60, block.get_hash()))
print(block)

Next, we repeat the process by creating another candidate block.
block = Block(
prev_block=block.get_hash(),
timestamp=int(time.time()),
transactions=transactions,
difficulty=difficulty
)
That’s it! There are a lot of things that were not implemented, but it gives you a basic idea of different data structures within a blockchain.

If you want to use a blockchain, chances are it would be better to use a database instead.
Here is the full code:
import hashlib
import json
import time
class Block:
def __init__(self, prev_block, timestamp, transactions, difficulty):
self.version = 1
self.prev_block = prev_block
self.timestamp = timestamp
self.difficulty = difficulty
self.nonce = 0
self.transactions = transactions
self.merkle_root = self.get_merkle_root()
def __str__(self):
data = {}
for attr in ["version", "prev_block","timestamp","difficulty","nonce","transactions","merkle_root"]:
data[attr] = getattr(self, attr)
return json.dumps(data, indent=2)
def get_hash(self):
return hashlib.sha256(str.encode(str(self))).hexdigest()
def get_merkle_root(self):
hashes = []
for transaction in self.transactions:
transaction_str = json.dumps(transaction)
transaction_hash = hashlib.sha256(str.encode(transaction_str)).hexdigest()
hashes.append(transaction_hash)
while len(hashes) > 1:
if len(hashes) % 2:
hashes.append(hashes[-1])
new_hashes = []
for i in range(0, len(hashes)-2+1, 2):
new_hash = hashlib.sha256(str.encode(str(hashes[i:i+2]))).hexdigest()
new_hashes.append(new_hash)
hashes = new_hashes
return hashes[0]
def is_valid(self):
difficulty_test_pass = int(self.get_hash(),16) < int(self.difficulty,16)
return difficulty_test_pass
def set_nonce(self, nonce):
self.nonce = nonce
def set_timestamp(self, timestamp):
self.timestamp = timestamp
def main():
version=1
difficulty="0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
transactions = [
{
"version":1,
"inputs":{"coinbase":50},
"outputs":{"my_email@yahoo.com":50}
}
]
block = Block(
prev_block="0x"+"0"*64,
timestamp=int(time.time()),
transactions=transactions,
difficulty=difficulty
)
while True:
nonce = 0
is_valid = False
print("\nMining...")
begin = time.time()
while not is_valid:
block.set_nonce(nonce)
is_valid = block.is_valid()
nonce += 1
if not nonce % 100000:
block.set_timestamp(int(time.time()))
print("Found valid block in {0:0.1f} minutes: {1}".format((time.time()-begin)/60, block.get_hash()))
print(block)
block = Block(
prev_block=block.get_hash(),
timestamp=int(time.time()),
transactions=transactions,
difficulty=difficulty
)
if __name__ == "__main__":
main()