Proof of Stake: A Python Example
In this post, I will walk through the creation of a basic PoS blockchain using Python, illustrating the foundational principles that make this technology so powerful. By the end, you’ll have a clearer understanding of how PoS blockchains work and a hands-on example that you can expand upon in your own projects. Whether you’re a seasoned developer or just starting out, this guide will help you grasp the essentials of blockchain technology in a practical, approachable way.
If you prefer a PoW blockchain example, see this post.
The Code
As a note, this code could be utilized in any coding environment but I would recommend a Jupyter Notebook. A version of the code mentioned in this post can be viewed on GitHub.
Import a few packages that will be useful.
import hashlib
import json
from datetime import datetime
import random
Start by defining the Block and Blockchain classes.
Create the Block Class
The Block
class will represent a single block in the blockchain. Each block will contain the block’s index, previous block’s hash, timestamp, data (transactions), nonce, and it’s own hash. The compute_hash
method calculates the SHA-256 hash of the block’s contents.
class Block:
def __init__(self, index, previous_hash, timestamp, data, validator):
self.index = index
self.previous_hash = previous_hash
self.timestamp = timestamp
self.data = data
self.validator = validator
self.hash = self.calculate_hash()
def calculate_hash(self):
block_dict = self.__dict__
if 'hash' in block_dict:
del block_dict['hash']
block_string = json.dumps(block_dict, sort_keys=True).encode()
return hashlib.sha256(block_string).hexdigest()
Create the Blockchain Class
The Blockchain
class will manage the chain of blocks and handle the consensus mechanism. The class defines the following methods:
-
The constructor initializes the blockchain instance. It sets up several important properties:
- self.chain: A list that holds the blocks of the blockchain.
- self.unconfirmed_data: A list of new data (or transactions) that have not yet been added to a block.
- self.validators: A dictionary where each validator is recorded. Validators are participants who can be selected to create blocks.
- self.staked_tokens: A dictionary that holds the amount of staked tokens for each validator. Staked tokens determine a validator’s chance of being selected.
- self.minimum_stake: The minimum amount of tokens a validator must stake to participate in the network.
- self.create_genesis_block(): The genesis block is the first block in the blockchain. It is created immediately when the blockchain is initialized.
-
create_genesis_block: This method creates the first block, or “genesis block,” in the blockchain. The genesis block typically has unique properties like an index of 0, no previous hash (since it’s the first), and no validator. It is appended to self.chain to start the blockchain.
-
last_block: This method returns the most recent block in the blockchain. It is used when creating new blocks to reference the last block’s hash.
-
add_data: This method adds new, unconfirmed data (or transactions) to the self.unconfirmed_data list. This data will eventually be included in the next block when it’s created.
-
add_validator: This method allows validators to join the blockchain. It takes two parameters: the validator and their stake, which is the amount of cryptocurrency they are willing to lock up. If the stake is greater than or equal to the minimum_stake, the validator is added to both self.validators and self.staked_tokens dictionaries. Validators with stakes below the threshold are rejected with an error message.
-
select_validator: This method selects a validator to create the next block, using a weighted random approach based on the amount of staked tokens. It works as follows:
- It first calculates the total_stake, which is the sum of all validators’ stakes.
- A random number (pick) is generated between 0 and total_stake.
- The method iterates over the self.staked_tokens dictionary, accumulating the stake values. When the accumulated value surpasses pick, that validator is selected.
- This method ensures that validators with higher stakes have a better chance of being selected, but randomness is involved to prevent deterministic outcomes.
-
create_block: This method creates a new block on the blockchain. It performs the following actions:
- It checks if there is any unconfirmed data in
self.unconfirmed_data
. If there is no data, no block is created, and the method returns False. - The method gets the most recent block (
last_block
) and creates a new Block object. The new block’s index is one greater than the last_block’s index, and itsprevious_hash
refers to the last block’s hash. - The block is created with the current timestamp, the unconfirmed data, and the selected validator who is creating this block.
- The new block is appended to the chain, and the
self.unconfirmed_data
list is cleared since the data has now been confirmed in a block. The method returns the index of the new block.
- It checks if there is any unconfirmed data in
-
display_chain: This method loops through the entire blockchain and prints each block’s details. It prints the attributes of each block by converting its dictionary representation
block.__dict__
.
class Blockchain:
def __init__(self):
self.chain = []
self.unconfirmed_data = []
self.validators = {}
self.staked_tokens = {}
self.minimum_stake = 10
self.create_genesis_block()
def create_genesis_block(self):
genesis_block = Block(0, None, str(datetime.now()), "Genesis Block", None)
self.chain.append(genesis_block)
def last_block(self):
return self.chain[-1]
def add_data(self, new_data):
self.unconfirmed_data.append(new_data)
def add_validator(self, validator, stake):
if stake >= self.minimum_stake:
self.staked_tokens[validator] = stake
self.validators[validator] = True
else:
print(f"{validator} does not meet the minimum stake requirement.")
def select_validator(self):
total_stake = sum(self.staked_tokens.values())
selected_validator = None
while selected_validator == None:
pick = random.uniform(0, total_stake)
print(pick)
current = 0
for validator, stake in self.staked_tokens.items():
print(validator, stake)
current += stake
if current > pick:
selected_validator = validator
break
return selected_validator
def create_block(self, validator):
if not self.unconfirmed_data:
return False
last_block = self.last_block()
new_block = Block(index=last_block.index + 1,
previous_hash=last_block.hash,
timestamp=str(datetime.now()),
data=self.unconfirmed_data,
validator=validator)
self.chain.append(new_block)
self.unconfirmed_data = []
return new_block.index
def display_chain(self):
for block in self.chain:
print(f"Block {block.__dict__}")
Running the code
When running this example, it will create a simple Blockchain, add some transactions, mine new blocks, and print the Blockchain’s contents. It also checks the validity of the entire chain to ensure its integrity.
# Create a Blockchain object
blockchain = Blockchain()
# Add some validators
blockchain.add_validator(999, 10)
blockchain.add_validator(1111, 50)
blockchain.add_validator(1222, 30)
blockchain.add_validator(1333, 20)
blockchain.add_validator(1444, 20)
# Add some data / transactions
blockchain.add_data("James earned 10 LSU_tokens")
blockchain.add_data("James pays Mike_the_tiger 5 LSU_tokens")
# Obtain a validator
selected_validator = blockchain.select_validator()
print(f"Validator selected: {selected_validator}")
109.81061787493455
999 10
1111 50
1222 30
1333 20
Validator selected: 1333
blockchain.create_block(selected_validator)
blockchain.display_chain()
Block {'index': 0, 'previous_hash': None, 'timestamp': '2024-09-15 17:10:14.873731', 'data': 'Genesis Block', 'validator': None, 'hash': '8e507213fcc4bdf99f2d86bbe76fc0f9d079152263b74a7a6968efa8b379fe4f'}
Block {'index': 1, 'previous_hash': '8e507213fcc4bdf99f2d86bbe76fc0f9d079152263b74a7a6968efa8b379fe4f', 'timestamp': '2024-09-15 17:11:42.801168', 'data': ['James earned 10 LSU_tokens', 'James pays Mike_the_tiger 5 LSU_tokens'], 'validator': 1333, 'hash': '469159d4f06a11514dda30d273ade304a3671b8117a754e6dcd450268586a446'}
Conclusion
This is a basic implementation of a Proof of Stake (PoS) blockchain, where validators stake tokens to increase their chances of being selected to create the next block. The selection is randomized but weighted based on how much stake each validator has. The PoS consensus method was created to address shortcomings with the Proof of Work, PoW, consensus method.