Blockchain in Python
When I read an article about
JavaScript blockchain
, I was interested to get acquainted with ideas about blockchain development that are different from those that I already know. And as soon as I started reading the code, I wanted to compare it with similar Python code in order to also understand its differences from the code written in JavaScript.
The purpose of this material is to identify differences in languages. Think of it as a Python add-on to the original article.
Despite the fact that the original article appeared after its author got acquainted with example blockchain development in Python, I wanted to write Python code that reproduces the JavaScript code from the article as closely as possible. This will allow comparison of blockchain implementation in different languages.
I am also going to make my blockchain implementation, like in the JavaScript article, also fit in 60 lines.
About blockchain
Although I was going to repeat the structure of that material in order to arrive at the finished code in the same way as its author, I will nevertheless include something of my own here. In particular, I prefer a different definition of blockchain. It reads like this: “Blockchain is a system for registering information, performed in a way that makes it difficult or impossible to change information, hack the system or fraudulent information.”
Preparing the development environment
In this project we will be using Python, so if you don’t have it installed, find a distribution that suits your OS and install it.
Block creation
A block is an object that contains some information. Therefore, let’s start by creating a class
Block
:
class Block:
def __init__(self, timestamp=None, data=None):
self.timestamp = timestamp or time()
# В this.data должна храниться информация, вроде сведений о транзакциях.
self.data = [] if data is None else data
Class definitions
Block
Python and JavaScript are very similar. In Python, instead of
this
used by
self
, and the analogue of the method
constructor
is an
init
…
Comments are executed in a similar way too. Python uses the symbol to style single-line comments #
, and in JavaScript – the construction //
…
Algorithm implementation sha256
I took from the library hashlib… In a JS project, it is taken from the package crypto
…
from hashlib import sha256
class Block:
def __init__(self, timestamp=None, data=None):
self.timestamp = timestamp or time()
self.data = [] if data is None else data
self.hash = self.getHash()
self.prevHash = None # Хеш предыдущего блока
def getHash(self):
hash = sha256()
hash.update(str(self.prevHash).encode('utf-8'))
hash.update(str(self.timestamp).encode('utf-8'))
hash.update(str(self.data).encode('utf-8'))
return hash.hexdigest()
In method
getHash
it all starts with an empty hash, which we form using the data stored in the block. The hash is calculated based on the hash of the previous block, the time stamp, and the data stored in the block. All of this is converted into a sequence of bytes using the method
.encode('utf-8')
…
Blockchain
Let’s take a class
Blockchain
…
class Blockchain:
def __init__(self):
# В этом свойстве будут содержаться все блоки.
self.chain = []
The blockchain classes are similar in both languages.
To create a primary block, we simply call Block
with the current time stamp, for which we use time()
… To do this, we need to import the library time
…
Converting a number to a string is done in Python using the function str()
rather than using the method toString()
as done in JavaScript.
from time import time
class Blockchain:
def __init__(self):
# Создаём первичный блок
self.chain = [Block(str(int(time())))]
The method for getting the freshest block looks similar. Only here, unlike a JavaScript project, to find out the length of the block chain, instead of the property
length
function is used
len()
…
def getLastBlock(self):
return self.chain[len(self.chain) - 1]
To add a block to the blockchain, we simply call the method
addBlock
… The code turned out to be almost the same as in the JS project, only here, instead of the method
push()
, the method is used
append()
…
def addBlock(self, block):
# Так как мы добавляем новый блок, prevHash будет хешем предыдущего последнего блока.
block.prevHash = self.getLastBlock().hash
# Так как теперь в prevHash имеется значение, мы должны пересчитать хеш блока.
block.hash = block.getHash()
self.chain.append(block)
Blockchain verification
In the method used to validate the blockchain, we use the function
range()
… This is a major difference between our code and the JS project code. And besides, since we don’t use constants in Python, we just use regular variables here.
When checking conditions in Python, instead of ||
, used or
…
def isValid(self):
# Перед перебором цепочки блоков нужно установить i в 1, так как до первичного блока никаких блоков нет. В результате мы начинаем со второго блока.
for i in range(1, len(self.chain)):
currentBlock = self.chain[i]
prevBlock = self.chain[i - 1]
# Проверка
if (currentBlock.hash != currentBlock.getHash() or prevBlock hash != currentBlock.prevHash):
return False
return True
Proof of Work Algorithm
We can implement a proof-of-work algorithm by starting by adding to the class
Block
method
mine()
and properties
nonce
… It is worth being careful here, since the property
nonce
must be declared before calling the method
self.getHash()
… Otherwise, an error will be thrown.
AttributeError: 'Block' object has no attribute 'nonce'
…
class Block:
def __init__(self, timestamp=None, data=None):
self.timestamp = timestamp or time()
self.data = [] if data is None else data
self.prevHash = None # хеш предыдущего блока
self.nonce = 0
self.hash = self.getHash()
# Наша хеш-функция.
def getHash(self):
hash = sha256()
hash.update(str(self.prevHash).encode('utf-8'))
hash.update(str(self.timestamp).encode('utf-8'))
hash.update(str(self.data).encode('utf-8'))
hash.update(str(self.nonce).encode('utf-8'))
return hash.hexdigest()
def mine(self, difficulty):
# Тут запускается цикл, работающий до тех пор, пока хеш не будет начинаться со строки
# 0...000 длины <difficulty>.
while self.hash[:difficulty] != '0' * difficulty:
# Инкрементируем nonce, что позволяет получить совершенно новый хеш.
self.nonce += 1
# Пересчитываем хеш блока с учётом нового значения nonce.
self.hash = self.getHash()
Let’s create a property that will store the complexity:
self.difficulty = 1
Let’s edit the method
addBlock
:
def addBlock(self, block):
block.prevHash = self.getLastBlock().hash
block.hash = block.getHash()
block.mine(self.difficulty)
self.chain.append(block)
Blockchain testing
Let’s import the module and use the class
Blockchain
just like the same class in a JS project:
from blockchain import Block
from blockchain import Blockchain
from time import time
JeChain = Blockchain()
# Добавим новый блок
JeChain.addBlock(Block(str(int(time())), ({"from": "John", "to": "Bob", "amount": 100})))
# (Это - всего лишь интересный эксперимент, для создания настоящей криптовалюты обычно нужно сделать намного больше, чем сделали мы).
# Вывод обновлённого блокчейна
print(JeChain)
The output from this code should look something like this:
[
{
"data": [],
"timestamp": "1636153236",
"nonce": 0,
"hash": "4caa5f684eb3871cb0eea217a6d043896b3775f047e699d92bd29d0285541678",
"prevHash": null
},
{
"data": {
"from": "John",
"to": "Bob",
"amount": 100
},
"timestamp": "1636153236",
"nonce": 14,
"hash": "038f82c6e6605acfcad4ade04e454eaa1cfa3d17f8c2980f1ee474eefb9613e9",
"prevHash": "4caa5f684eb3871cb0eea217a6d043896b3775f047e699d92bd29d0285541678"
}
]
But all this will work only after adding it to the class.
Blockchain
method
__repr__()
:
import json
def __repr__(self):
return json.dumps([{'data': item.data, 'timestamp': item.timestamp, 'nonce': item.nonce, 'hash': item.hash, 'prevHash': item.prevHash} for item in self.chain], indent=4)
Addition: difficulty and block time
To set the block time, we need the corresponding property:
self.blockTime = 30000
Let’s take a look at the ternary operator used in the difficulty setting system. If we rewrite the JS construct with the ternary operator in Python, we get the following:
(if_test_is_false, if_test_is_true)[test]
:
def addBlock(self, block):
block.prevHash = self.getLastBlock().hash
block.hash = block.getHash()
block.mine(self.difficulty)
self.chain.append(block)
self.difficulty += (-1, 1)[int(time()) - int(self.getLastBlock().timestamp) < self.blockTime]
The final Python code (without normal formatting) fits into the promised 60 lines:
# -*- coding: utf-8 -*-
from hashlib import sha256
import json
from time import time
class Block:
def __init__(self, timestamp=None, data=None):
self.timestamp = timestamp or time()
self.data = [] if data is None else data
self.prevHash = None
self.nonce = 0
self.hash = self.getHash()
def getHash(self):
hash = sha256()
hash.update(str(self.prevHash).encode('utf-8'))
hash.update(str(self.timestamp).encode('utf-8'))
hash.update(str(self.data).encode('utf-8'))
hash.update(str(self.nonce).encode('utf-8'))
return hash.hexdigest()
def mine(self, difficulty):
while self.hash[:difficulty] != '0' * difficulty:
self.nonce += 1
self.hash = self.getHash()
class Blockchain:
def __init__(self):
self.chain = [Block(str(int(time())))]
self.difficulty = 1
self.blockTime = 30000
def getLastBlock(self):
return self.chain[len(self.chain) - 1]
def addBlock(self, block):
block.prevHash = self.getLastBlock().hash
block.hash = block.getHash()
block.mine(self.difficulty)
self.chain.append(block)
self.difficulty += (-1, 1)[int(time()) - int(self.getLastBlock().timestamp) < self.blockTime]
def isValid(self):
for i in range(1, len(self.chain)):
currentBlock = self.chain[i]
prevBlock = self.chain[i - 1]
if (currentBlock.hash != currentBlock.getHash() or prevBlock.hash != currentBlock.prevHash):
return False
return True
def __repr__(self):
return json.dumps([{'data': item.data, 'timestamp': item.timestamp, 'nonce': item.nonce, 'hash': item.hash, 'prevHash': item.prevHash} for item in self.chain], indent=4)
I hope you enjoyed both content and found something useful in them.
If you needed to create a blockchain system – what tools would you use?