A Pythonic blockchain
Making use of the Python standard library.
Source code on GitHub. I’m particularly proud of
- use of
defaultdict
andnamedtuple
from the standard library - inheriting from list for the
Blockchain
andNetwork
classes
Transactions and blocks are represented using namedtuple
. Both are immutable - use of namedtuple
allows indexing by key and take care of stuff like __repr__
. They also represent the most primitive objects in our blockchain network.
The objects in our network are
- one network
- n nodes
- c blockchains (one per node)
- b blocks (per chain)
- t transactions (per block)
For example, we could have a network with two nodes (miners or account holders). Each of these holds a blockchain - the same blockchain if the network is currently in consensus. Each blockchain is made of multiple blocks, with each block having a number of transactions.
Transaction = namedtuple(
'transaction',
['sender',
'to',
'amount',
'signature'])
Block = namedtuple(
'block',
['index',
'timestamp',
'proof',
'previous_hash',
'hash',
'transactions'])
A blockchain is represented as a class, inheriting from list. This gives us functionality like indexing and appending for free. The elements of this list are blocks.
class BlockChain(list):
def __init__(self):
genesis_block = OrderedDict(
{'index': 1,
'timestamp': date.datetime.now(),
'proof': 9,
'previous_hash': 'hello world',
'transactions': []}
)
self.append(
Block(
**genesis_block,
hash=get_hash(genesis_block))
)
def update_transactions(self, new):
return self[-1].transactions + new
def next_block(self, new_transactions, proof):
last_block = self[-1]
next_block = OrderedDict(
{'index': len(self) + 1,
'timestamp': date.datetime.now(),
'proof': proof,
'previous_hash': last_block.hash,
'transactions' : self.update_transactions(new_transactions)}
)
self.append(
Block(**next_block, hash=get_hash(get_hash(next_block)))
)
A node (i.e. miner or holder of tokens) is a simple class. It holds infomation such as public and private keys, along with functionality to make, sign and verify transactions. Each node holds it’s own instance of the BlockChain
class.
The final major object is the Network
, implemented as a class that inherits from list. The network is composed of multiple nodes. The network performs the consensus algorithm (in this case checking for the longest chain). It also validates transactions.
class Network(list):
""" should never have a BlockChain - only nodes have chains """
def __init__(
self,
nodes,
overdraft_limit
):
list.__init__(self, nodes)
self.proof = 9
self.overdraft_limit = overdraft_limit
def proof_of_work(self):
self.proof = self.proof + 0.1
sleep(self.proof)
return self.proof
def consensus(self):
""" find the node that mined the block """
chains = [node.chain for node in self]
new_chain = chains[np.argmax([len(chain) for chain in chains])]
for node in self:
node.chain = new_chain
def validate_transactions(self, balances, new_transactions):
assert self.check_balances(balances)
validated = []
for transaction in new_transactions:
print('processing to:{} sender:{} amount:{}'.format(
transaction.to, transaction.sender, transaction.amount))
amount = float(transaction.amount)
new_bal = balances[transaction.sender] - amount
if new_bal < self.overdraft_limit:
print('rejected - {} overdrawn with bal of {}'.format(
transaction.sender, new_bal))
else:
print('accepted')
balances[transaction.sender] -= amount
balances[transaction.to] += amount
validated.append(transaction)
assert self.check_balances(balances)
return balances, validated
def check_balances(self, balances):
""" checks that all balances are over the limit """
for node, balance in balances.items():
assert balance >= self.overdraft_limit
return True
Putting it all together - the code below simulates three blocks being added with random transactions
if __name__ == '__main__':
net = Network([Node('node'), Node('other')])
transactions = net[0].chain[-1].transactions
for _ in range(3):
# simulate a few transactions between nodes
new_transactions = simulate_transactions(net[0], net[1])
# check the transactions are valid
transactions = validate_transactions(
transactions, new_transactions)
new_proof = net.proof_of_work()
# randomly select a miner
miner = net[np.random.randint(len(net))]
# miner adds the block to it's chain
miner.add_next_block(new_transactions, proof=new_proof)
# update other nodes in the network
net.consensus()
Next step for this work is to distribute the blockchain over multiple processes using Flask.
Thanks for reading!
Resources and references
Python’s Class Development Toolkit - Raymond Hettinger - youtube
Effective Python - Brett Slatkin - book
ecomusing - blog post - GitHub
Gerald Nash - blog post one - blog post two