You've probably come across the term blockchain
"the technology behind cryptocurrencies" by now. But what is a blockchain? And how to build one?
What is a blockchain?
A blockchain is, in short, a linked list of blocks, where each block added attests to the veracity of the block immediately before it, guaranteeing the legitimacy of the data. Despite being a simplistic explanation, in short, this is how a blockchain works.
As the concept is a bit confusing, I think it's better to see how a blockchain works in practice, that is, creating one from scratch.
Starting development
First let's add the following dependencies to the file:
import re
import json
from datetime import datetime as dt
from hashlib import sha256
Each of these dependencies will be explained later in the code, but add them because they will be needed. Now, let's get to the code itself.
The block
A block is, in short, an object that has:
- an ID
- an identification hash
- The hash of the previous block (to ensure integrity)
- Content (block data)
- Transaction date and time
Of course a block can have more or less attributes, but this is the most common structure of a blockchain we have. The blocks, as they are chained together, end up behaving like a "chain of blocks", where you are able to pull data from all previous blocks starting with the last one recorded.
Now, let's create a class Block
with this data:
class Block:
def __init__(self, index, previous_hash, data):
self.index = index
self.previous_hash = previous_hash
self.data = data
self.timestamp = dt.now().timestamp()
Did you notice something strange? Exactly, we are not generating the current block hash:
class Block:
# código do construtor
def generate_hash(self):
return sha256(str( \
str(self.index) + \
str(self.previous_hash) + \
json.dumps(self.data) + \
str(self.timestamp)).encode('utf-8') \
).hexdigest()
This function will convert the block data into a JSON, concatenate with all the other data and create a cryptographic hash sha256
from that string. This is the hash of the block, which validates its integrity. O sha256
is just one of the algorithms, but you can use several others, like o md5
(although I don't recommend it) or other cryptographic hashing algorithms. You will assign the property to the class as self.hash = self.generate_hash()
.
So we have this as our starting block:
class Block:
def __init__(self, index, previous_hash, data):
self.index = index
self.previous_hash = previous_hash
self.data = data
self.timestamp = dt.now().timestamp()
self.hash = self.generate_hash()
def generate_hash(self):
return sha256(str( \
str(self.index) + \
str(self.previous_hash) + \
json.dumps(self.data) + \
str(self.timestamp)).encode('utf-8') \
).hexdigest()
the blockchain
A blockchain works as if it were a chained list of blocks, where the last attached block is related to the penultimate by its hash. This is what will guarantee its validation and ensure that each block is legitimate. The initial structure will look like this:
class Blockchain:
def __init__(self):
self.blocks = [Block(0, None, {'content': 'Bloco Inicial'})]
self.index = 1
Of course, the user will not be manually entering the ID and hash of the blocks, he will just give you a JSON containing the block data and want it to be there the way it should. So let's add the corresponding block:
class Blockchain:
# código do construtor
def get_last_block(self):
return self.blocks[-1]
def add_new_block(self, data):
previous_hash = self.get_last_block().hash
block = Block(self.index, previous_hash, data)
self.index += 1
self.blocks.append(block)
We can test if everything is working correctly as follows:
chain = Blockchain()
chain.add_new_block({'content': {'transaction_01': 1, 'transaction_02': 2}})
print(chain.blocks[1].data)
This code will return a dictionary with the following structure:
{
'content': {
'transaction_01': 1, 'transaction_02': 2
}
}
We can also see the block hash if we want:
print(chain.blocks[1].hash)
This will return a random string, just like the string below:
2d76eeac710e20749fb1d594b9caf552ec93733d1205593f4c27e1488509e7f4
Validating blocks
Of course, with the code we have, anyone can change some data in our chain and it would be difficult to validate. Note my change:
chain.blocks[1].data = {'content': {'transaction_01': 3000}}
This doesn't feel right, does it? After all, if I made a transaction, I want it to be unique, and if someone changes it, the entire chain becomes invalid. So let's add a validation function to the blockchain:
class Blockchain:
# demais funções
def is_valid(self):
for i in range(len(self.blocks)):
current_block = self.blocks[i + 1]
previous_block = self.blocks[i]
if current_block.hash != current_block.generate_hash():
return False
if current_block.index != (previous_block.index + 1):
return False
if current_block.previous_hash != previous_block.hash:
return False
return True
This function performs the following operations:
- cycles through the chain
- Check if the values are in the correct order
- Checks if the hash of the block matches the hash of its successor
- Check if the hash is valid
We can now test our function:
chain = Blockchain()
chain.add_new_block({'content': {'transaction_01': 1, 'transaction_02': 2}})
print(chain.is_valid()) # True
chain.blocks[1].data = {'content': {'transaction_01': 3000}}
print(chain.is_valid()) # False
If you've followed all the steps so far, there's probably something like this:
class Blockchain:
def __init__(self):
self.blocks = [Block(0, None, {'content': 'Bloco Inicial'})]
self.index = 1
def get_last_block(self):
return self.blocks[-1]
def add_new_block(self, data):
previous_hash = self.get_last_block().hash
block = Block(self.index, previous_hash, data)
self.index += 1
self.blocks.append(block)
def is_valid(self):
for i in range(len(self.blocks)):
current_block = self.blocks[i + 1]
previous_block = self.blocks[i]
if current_block.hash != current_block.generate_hash():
return False
if current_block.index != (previous_block.index + 1):
return False
if current_block.previous_hash != previous_block.hash:
return False
return True
Mining
Now we get one more problem: I can just loop and keep adding garbage to the chain. Of course we don't want that, because if we keep adding garbage to our blockchain, it will become gigantic and useless. Note below:
chain = Blockchain()
i = 0
while(True):
i += 1
chain.add_new_block({'content': i})
This will simply fill our blockchain with useless and meaningless blocks. We need to add a certain "work", an extra difficulty, to make sure the person adding that block REALLY wants to add it. One way we can do this is mining.
The hashes in sha256
are generated from an algorithm, so they're not really random, but it takes some computational power to get a valid hash for a given block. Mining is exactly that. During the mining process, you have a difficulty that varies from 0 to the width of the algorithm's hash (in our case, 64). If the difficulty is 1, for example, the first character of the hash must be 0
, if the difficulty is 2 the first and second character must be 0
, if it is 3 the first, the second and third character must be zero, and so on. against. To obtain these hashes, the computer will keep creating tens, hundreds, thousands or even millions of hashes, until it finds one that matches the selected difficulty. So let's start by creating a function mine
to our block:
class Block:
def __init__(self, index, previous_hash, data, difficulty):
self.index = index
self.previous_hash = previous_hash
self.data = data
self.timestamp = dt.now().timestamp()
self.difficulty = difficulty
self.mine()
def mine(self):
self.hash = self.generate_hash()
while re.match("^[0]+$", self.hash[:self.difficulty]) == None:
self.nonce += 1
self.hash = self.generate_hash()
And we'll add the difficulty to our blockchain:
class Blockchain:
def __init__(self):
self.blocks = [Block(0, None, {'content': 'Bloco Inicial'}), 1]
self.index = 1
self.difficulty
def add_new_block(self, data):
previous_hash = self.get_last_block().hash
block = Block(self.index, previous_hash, data, self.difficulty)
self.index += 1
self.blocks.append(block)
It will now take some time for each block to be mined (random time, of course, but it will still be some time), which will prevent them from filling your chain with useless stuff.
The complete code can then be summarized as:
import re
import json
from datetime import datetime as dt
from hashlib import sha256
class Block:
def __init__(self, index, previous_hash, data, difficulty):
self.index = index
self.previous_hash = previous_hash
self.data = data
self.timestamp = dt.now().timestamp()
self.difficulty = difficulty
self.mine()
def mine(self):
self.hash = self.generate_hash()
while re.match("^[0]+$", self.hash[:self.difficulty]) == None:
self.nonce += 1
self.hash = self.generate_hash()
def generate_hash(self):
return sha256(str( \
str(self.index) + \
str(self.previous_hash) + \
json.dumps(self.data) + \
str(self.timestamp)).encode('utf-8') \
).hexdigest()
class Blockchain:
def __init__(self):
self.blocks = [Block(0, None, {'content': 'Bloco Inicial'}), 1]
self.index = 1
self.difficulty
def get_last_block(self):
return self.blocks[-1]
def add_new_block(self, data):
previous_hash = self.get_last_block().hash
block = Block(self.index, previous_hash, data, self.difficulty)
self.index += 1
self.blocks.append(block)
def is_valid(self):
for i in range(len(self.blocks)):
current_block = self.blocks[i + 1]
previous_block = self.blocks[i]
if current_block.hash != current_block.generate_hash():
return False
if current_block.index != (previous_block.index + 1):
return False
if current_block.previous_hash != previous_block.hash:
return False
return True
And presto, you have your first blockchain developed in Python.