Building Layer 1 blockchain from scratch (PART — I wallet)
In this series, we’re building a Layer 1 blockchain from scratch using Python, focusing on four key components:
- Wallet
- Blockchain & Mining
- DB
- P2P
In this first part, we’ll start by creating a simple wallet to generate key pairs, sign transactions, and interact with our blockchain.
Environment:
Python Version: 3.11.6
Operating System: Linux Ubuntu
Database: LevelDB
Wallet
The wallet is the starting point for interacting with any blockchain. In this section, we’ll build a simple wallet in Python that can generate Ethereum-compatible public and private key pairs, sign transactions, and interact with our custom blockchain. This wallet will serve as the tool for managing account balances and authorising transactions securely.
Code:
1. Initialise the Blockchain
#Eth moduels
from eth_account import Account
from eth_account.messages import encode_defunct
#Custom modules
from blockchain import Blockchain
from db import get_account_state, get_transaction, update_account_state
# Entry point of the script
if __name__ == "__main__":
# Initialize the Blockchain instance
blockchain = Blockchain()
- The script begins by creating an instance of the
Blockchain
class, which will manage all transactions and blocks in the system. We will learn more about the class in the next article
2. Setup Sender and Receiver Accounts
# Define the sender's Ethereum address and private key
sender_address = '0xbd281AE5D72050dEB0243b91a81018709AFA1994'
sender_key = '39092b4d8f20dd79c73928e501230b714a7730956755738be7523b7a19773ece'
# Create an account object for the sender using the private key
sender_account = Account.from_key(sender_key)
# Update the sender's account balance to 1,000,000 units
update_account_state(sender_address, 1_000_000)
# Dynamically create a new Ethereum account for the receiver
receiver_account = Account.create()
- A predefined sender account is initialised using an Ethereum address and private key (
Account.from_key
). - The sender’s account balance is updated to 1,000,000 units using the
update_account_state
function. - A new Ethereum account is dynamically created for the receiver using
Account.create
. Every transaction will have a new receiver.
3. Create and Sign a Transaction
amount = 10 # amount to be transferred
# Create a message string that includes sender, receiver, and amount details
message = f"{sender_account.address}:{receiver_account.address}:{amount}"
# Encode the message to be compatible with Ethereum signing process
message = encode_defunct(text=message)
# Sign the encoded message with the sender's private key to produce a signature
signature = Account.sign_message(message, sender_account.key).signature.hex()
# Create a transaction dictionary containing all necessary transaction details
transaction = {
'Sender': sender_account.address,
'Receiver': receiver_account.address,
'Amount': str(amount),
'signature': signature
}
- A transaction message is generated, containing the sender’s address, receiver’s address, and the amount to be transferred.
- This message is then signed with the sender’s private key using
Account.sign_message
, ensuring the transaction's authenticity.
4. Add the Transaction to the Blockchain
# Loop to add multiple transactions (simulating 10,000 transactions)
for x in range(10_000):
# Add the transaction to the blockchain and get a unique transaction key (txn_key)
txn_key = blockchain.add_transaction(transaction)
# Print the details of the transaction and current balance of sender and receiver
print(f"{sender_account.address[-5:]} : {amount} --> {receiver_account.address[-5:]}")
print(f"{sender_account.address[-5:]} Balance = {get_account_state(sender_account.address)['balance']}\n
{receiver_account.address[-5:]} Balance = {get_account_state(receiver_account.address)['balance']}")
- The signed transaction is added to the blockchain using the
add_transaction
method which returns a unique key every time a transaction is done. The for loop simulates 10,000 transactions - After each addition, the balances of both sender and receiver are printed using
get_account_state
to track the transaction flow.
5. Display Blockchain Blocks’
# After all transactions, display all the blocks in the blockchain
for block in blockchain.blocks:
print(block)
- Once all transactions are added, the script prints out all the blocks created in the blockchain using a loop over
blockchain.blocks
, showing the results of the transactions.
6. Retrieve and Display the Transaction
# If the transaction key is valid, retrieve the transaction using the key and display it
if txn_key:
retrieved_transaction = get_transaction(txn_key)
if retrieved_transaction:
print("Retrieved transaction:", retrieved_transaction)
else:
print("Transaction not found.")
else:
print("Invalid transaction.")
- The script retrieves the transaction using its unique key with the
get_transaction
function. - If the transaction is found in the database, its details are printed to confirm it was successfully added to the blockchain; otherwise, an error message is displayed.
🏁 Conclusion and What’s Next
In this first part, we successfully built a simple wallet in Python that can generate key pairs, sign transactions, and interact with our custom blockchain.
In the next part of this series, we’ll dive deeper into the core of our blockchain by building the Blockchain
class. We’ll explore how transactions are validated, how blocks are created and linked together, and how consensus is maintained within the network.