Driver FixRecommendedSound, Wi-Fi or graphics acting up? Check drivers firstFind missing or outdated drivers fast.Check DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix Now×
Skip to content
The Finance Base
The Money Desk · Blog
Re:

Learn Blockchains by Building One: A Practical Python Tutorial

Build an educational blockchain in Python, add a Flask API, mine toy blocks, test transactions with cURL, and run two local nodes while understanding what the project cannot secure.
From TheFinanceBase Team12 min to read
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The fastest way to understand a blockchain is to build a small one. In this tutorial, you will create an educational blockchain in Python, connect it to a Flask API, add transactions, implement a simplified proof-of-work puzzle, run two local nodes, and resolve conflicting chains.

This project demonstrates how blocks, hashes, pending transactions, mining, and basic chain reconciliation fit together. It does not create a secure cryptocurrency or a production-ready decentralized network. That distinction matters especially if you are evaluating blockchain projects as investments: a linked list of hashed records is not the same thing as a secure financial system.

As an Amazon Associate I earn from qualifying purchases.

The exercise comes from Daniel van Flymen’s tutorial “Learn Blockchains by Building One,” published September 24, 2017. The original walkthrough remains useful, but its Python and dependency instructions are historical and should not be treated as current setup guidance.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What you will build

By the end, your local project will contain a toy blockchain node with:

  • A chain of linked blocks.
  • A genesis block that starts the chain.
  • Pending transactions waiting for inclusion in a block.
  • SHA-256 hashing and previous-block references.
  • A simple proof-of-work mining loop.
  • HTTP endpoints for submitting transactions and inspecting the chain.
  • Node registration and simplified longest-valid-chain reconciliation.

The result is valuable as a learning model. It is not a deployable cryptocurrency because it lacks digital signatures, secure identity, persistent storage, authenticated peer networking, economic incentives, robust fork choice, and several other properties required by real systems.

Prerequisites

You do not need prior blockchain expertise. You should be comfortable with:

  • Basic Python syntax, functions, classes, lists, dictionaries, loops, and sets.
  • JSON and basic command-line use.
  • HTTP methods, particularly GET and POST.
  • The general idea that a hash is a fixed-length fingerprint of data.

Virtual environments, REST APIs, SHA-256, localhost networking, and basic testing are helpful but optional.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use a current Python environment

The original tutorial specifies Python 3.6, Flask 0.12.2, and Requests 2.18.4. Those are 2017-era versions. The original repository documents a Pipenv-based setup, while the associated book repository uses Poetry and reflects a Python 3.8-era environment. For a new project, use an isolated environment and a supported Python release. The Python downloads page identifies Python 3.14 as the current bugfix line in the referenced research snapshot, with Python 3.12 and 3.13 also supported; Python 3.9 is end-of-life.

macOS or Linux

mkdir learn-blockchain
cd learn-blockchain
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
pip install Flask

Windows PowerShell

mkdir learn-blockchain
cd learn-blockchain
py -m venv .venv
.venvScriptsActivate.ps1
python -m pip install --upgrade pip
pip install Flask

The current Flask documentation is the appropriate reference for maintained Flask behavior.

Reproducing the historical tutorial

If your goal is to reproduce the original 2017 environment rather than build a maintained version, the article specifies:

pip install Flask==0.12.2 requests==2.18.4

Use this only as a legacy reproduction path. Old packages may fail to install or behave correctly on modern Python releases. Do not mix old pins into a new production project.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How the model works

Blocks and hash links

Each block in the tutorial contains:

  • index: the block’s position.
  • timestamp: when it was created.
  • transactions: transfers included in that block.
  • proof: the result of the proof-of-work puzzle.
  • previous_hash: the hash of the preceding block.

When a block stores the previous block’s hash, changing an earlier block changes its hash and makes later references inconsistent. This makes tampering detectable within the toy model.

It does not make records absolutely immutable. Real systems can experience reorganizations, competing histories, database compromise, key compromise, censorship, or control by a majority of the relevant consensus power. Hashing provides integrity evidence; it does not provide authorization, confidentiality, or financial safety.

The genesis block

The genesis block is the deliberate starting point of the chain. It has no genuine predecessor, so the implementation can create it with a fixed proof and a simple previous-hash value.

Every node in a real protocol must agree on the genesis block’s exact contents. Its fields, serialization, and hash must be deterministic. Otherwise, two nodes may begin with different histories. A toy implementation can be simpler, but the reason for the starting block remains the same: every later block needs a common reference point.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Build the blockchain core

Create a file named blockchain.py. The following compact implementation follows the tutorial’s learning sequence while adding basic validation and modern Python structure.

import hashlib
import json
import time
from urllib.parse import urlparse


class Blockchain:
    def __init__(self):
        self.current_transactions = []
        self.chain = []
        self.nodes = set()

        # Create the genesis block.
        self.new_block(proof=100, previous_hash="1")

    def register_node(self, address):
        parsed = urlparse(address)
        if parsed.scheme not in {"http", "https"} or not parsed.netloc:
            raise ValueError("A node must be a complete HTTP or HTTPS URL")
        self.nodes.add(parsed.netloc)

    def new_block(self, proof, previous_hash=None):
        block = {
            "index": len(self.chain) + 1,
            "timestamp": time.time(),
            "transactions": self.current_transactions,
            "proof": proof,
            "previous_hash": previous_hash or self.hash(self.chain[-1]),
        }

        self.current_transactions = []
        self.chain.append(block)
        return block

    def new_transaction(self, sender, recipient, amount):
        if not sender or not recipient:
            raise ValueError("sender and recipient are required")
        if isinstance(amount, bool) or not isinstance(amount, (int, float)):
            raise ValueError("amount must be numeric")
        if amount < 0:
            raise ValueError("amount cannot be negative")

        self.current_transactions.append({
            "sender": sender,
            "recipient": recipient,
            "amount": amount,
        })
        return self.last_block["index"] + 1

    @property
    def last_block(self):
        return self.chain[-1]

    @staticmethod
    def hash(block):
        # sort_keys gives equivalent dictionaries the same JSON field order.
        block_string = json.dumps(block, sort_keys=True, separators=(",", ":"))
        return hashlib.sha256(block_string.encode()).hexdigest()

    @staticmethod
    def valid_proof(last_proof, proof):
        guess = f"{last_proof}{proof}".encode()
        guess_hash = hashlib.sha256(guess).hexdigest()
        return guess_hash[:4] == "0000"

    def proof_of_work(self, last_proof):
        proof = 0
        while not self.valid_proof(last_proof, proof):
            proof += 1
        return proof

    @classmethod
    def valid_chain(cls, chain):
        if not chain:
            return False

        for index in range(1, len(chain)):
            previous = chain[index - 1]
            current = chain[index]

            if current["previous_hash"] != cls.hash(previous):
                return False
            if not cls.valid_proof(previous["proof"], current["proof"]):
                return False

        return True

Why canonical serialization matters

A hash is calculated from bytes, not from an abstract Python dictionary. Two nodes must serialize semantically identical blocks into exactly the same bytes. The example uses JSON with sorted keys and compact separators so field order is predictable.

A production protocol must define much more: number formatting, character encoding, null handling, signature encoding, field inclusion, and treatment of mutable values. Without those rules, two implementations can appear to hold the same block but calculate different hashes.

Adding transactions

A transaction has the illustrative shape:

{
  "sender": "my address",
  "recipient": "someone else's address",
  "amount": 5
}

The transaction goes into current_transactions and is assigned to the next block that gets mined.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Notice what this code does not prove. The sender field is merely text. Anyone can claim to be another user, there is no signature, no balance check, and no account or unspent-output model. Basic input validation prevents obvious malformed data, but it does not create a secure currency. Real authorization requires public/private-key cryptography and signature verification.

Proof of work

The simplified puzzle searches for a number whose combination with the previous proof produces a SHA-256 hash beginning with four zeroes:

def valid_proof(last_proof, proof):
    guess = f"{last_proof}{proof}".encode()
    guess_hash = hashlib.sha256(guess).hexdigest()
    return guess_hash[:4] == "0000"

Finding a proof requires repeated guesses, while checking one is comparatively cheap. Requiring more leading zeroes makes a valid result rarer and generally increases the expected work.

This is a teaching device, not a measurement of cryptocurrency security. The toy chain has no meaningful economic reward, no network-wide difficulty adjustment, no competitive mining market, and no hash-rate security model. Do not compare its four-zero rule directly with Bitcoin mining difficulty.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Expose the chain through Flask

Add the API layer below to the same file:

from flask import Flask, jsonify, request

app = Flask(__name__)
blockchain = Blockchain()


@app.get("/chain")
def full_chain():
    return jsonify({
        "chain": blockchain.chain,
        "length": len(blockchain.chain),
    })


@app.post("/transactions/new")
def new_transaction():
    values = request.get_json(silent=True) or {}
    required = {"sender", "recipient", "amount"}

    if not required.issubset(values):
        return jsonify({"error": "Missing values"}), 400

    try:
        index = blockchain.new_transaction(
            values["sender"], values["recipient"], values["amount"]
        )
    except ValueError as error:
        return jsonify({"error": str(error)}), 400

    return jsonify({"message": f"Transaction will be added to Block {index}"}), 201


@app.get("/mine")
def mine():
    last_proof = blockchain.last_block["proof"]
    proof = blockchain.proof_of_work(last_proof)

    # This is a toy reward, not a real currency issuance mechanism.
    blockchain.new_transaction(
        sender="0",
        recipient="toy-miner",
        amount=1,
    )
    block = blockchain.new_block(proof)

    return jsonify({
        "message": "New block forged",
        "index": block["index"],
        "transactions": block["transactions"],
        "proof": block["proof"],
        "previous_hash": block["previous_hash"],
    }), 200


if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser()
    parser.add_argument("-p", "--port", default=5000, type=int)
    args = parser.parse_args()
    app.run(host="127.0.0.1", port=args.port)

The original tutorial uses GET /mine for convenience. In a modern API, mining changes server state, so POST /mine would usually better match HTTP semantics. You can preserve the historical route for compatibility while designing new code with a state-changing method.

Run and test the node

Start the server:

python blockchain.py

The original example uses port 5000 and displays a local address similar to:

Running on http://127.0.0.1:5000/

Mine a block

curl http://127.0.0.1:5000/mine

The response should contain a new block index, its proof, its previous hash, and the transactions included in it. The proof-of-work loop may take noticeably longer as you increase the difficulty.

Submit a transaction

curl -X POST 
  -H "Content-Type: application/json" 
  -d '{
    "sender": "d4ee26eee15148ee92c6cd394edd974e",
    "recipient": "someone-other-address",
    "amount": 5
  }' 
  http://127.0.0.1:5000/transactions/new

A successful response identifies the block number that will receive the transaction. The transaction remains pending until another block is mined.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Inspect the chain

curl http://127.0.0.1:5000/chain

Use the returned chain array to verify that the block’s previous_hash matches the hash of the preceding block and that transactions moved out of the pending list when mining occurred.

Postman can provide a graphical interface, but it is optional. cURL is sufficient, reproducible, and requires no account. The same requests can also be sent with HTTPie or an editor’s REST-client extension.

Run two nodes

Open two terminals, activate the virtual environment in both, and start separate processes:

python blockchain.py -p 5000
python blockchain.py -p 5001

Each process has its own in-memory chain. They are separate nodes only in the limited sense that they are separate local programs communicating over HTTP. Multiple localhost processes are not equivalent to a permissionless decentralized network.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Register a neighboring node

curl -X POST 
  -H "Content-Type: application/json" 
  -d '{"nodes": ["http://127.0.0.1:5001"]}' 
  http://127.0.0.1:5000/nodes/register

The original tutorial maintains a registry of neighboring node URLs. A production implementation would need URL validation, authentication or admission controls, TLS, request timeouts, retry limits, peer discovery, and protection against SSRF and malicious peers.

Resolve a conflict

The tutorial’s simplified rule is: ask neighboring nodes for their chains, validate them, and replace the local chain if a neighbor has a longer valid chain.

Conceptually, the resolver looks like this:

def resolve_conflicts(blockchain):
    neighbors = blockchain.nodes
    longest = blockchain.chain

    for node in neighbors:
        # A real implementation should use timeouts and handle failures.
        response = requests.get(f"http://{node}/chain", timeout=3)
        if response.status_code != 200:
            continue

        data = response.json()
        chain = data.get("chain", [])
        if len(chain) > len(longest) and blockchain.valid_chain(chain):
            longest = chain

    if longest is not blockchain.chain:
        blockchain.chain = longest
        return True
    return False

This rule is useful for demonstrating reconciliation, but “the longest chain wins” is not a universal blockchain consensus rule. Real systems may use accumulated proof-of-work, stake-weighted fork choice, finality mechanisms, validator rules, or other protocol-specific criteria. Chain length alone can be misleading.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Break the toy chain deliberately

Learning improves when you test failure cases rather than only the happy path.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Tamper with an earlier block

Change a transaction or timestamp in an existing block, then call valid_chain. The hash of that block no longer matches the next block’s previous_hash, so validation should return False.

This demonstrates tamper evidence. It does not prevent an attacker from rewriting the entire history and presenting a replacement chain if the surrounding protocol permits it.

Submit malformed JSON

curl -X POST 
  -H "Content-Type: application/json" 
  -d '{"sender":"only-one-field"}' 
  http://127.0.0.1:5000/transactions/new

The API should return a client error rather than silently creating an incomplete transaction. A production API should also enforce maximum lengths, exact numeric rules, rate limits, authentication, and a formal schema.

Use a negative amount

curl -X POST 
  -H "Content-Type: application/json" 
  -d '{"sender":"a","recipient":"b","amount":-5}' 
  http://127.0.0.1:5000/transactions/new

The example rejects negative values, but this is still not balance validation. A real ledger must define how balances are created, spent, checked, and protected against double spending.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use a busy port

If port 5000 is already occupied, the server cannot bind to it. Start another instance on a different port:

python blockchain.py -p 5001

The original repository also documents alternate ports such as 5001 and 5002.

Stop a peer

If a registered peer is unavailable, an unprotected HTTP request may hang or fail with a connection error. Add timeouts, catch network exceptions, and treat an unreachable peer as unavailable rather than valid. Never let a single peer prevent local chain inspection or normal API operation.

Restart a node

This in-memory implementation loses its chain when the process stops. Persistence requires a file or database, but persistence alone does not make a system decentralized or secure. You would still need consistency rules, crash recovery, validation, access control, and protection against conflicting writes.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What this project does not implement

Missing feature Why it matters
Digital signatures Without signatures, anyone can claim to be any sender.
Balance or UTXO validation The basic model does not prove that a sender owns or can spend funds.
Persistent storage Process restarts erase the in-memory state.
Merkle trees Transactions are stored directly in blocks rather than in a scalable authenticated tree.
Peer authentication Node URLs are not identities, and arbitrary peers can be unsafe.
Peer discovery Nodes must be told about one another manually.
Sybil resistance There is no mechanism to stop an attacker from creating many identities.
Difficulty adjustment The four-zero target does not adapt to changing computing power.
Economic incentives The mining reward has no real monetary value or enforceable monetary policy.
Robust fork choice Longest valid chain is only a teaching simplification.
Concurrency control Simultaneous requests can race to append or mine blocks.

The project therefore demonstrates several blockchain ingredients without reproducing the complete security model of Bitcoin, Ethereum, or another public network.

Modernize the tutorial responsibly

  1. Use an isolated environment. Keep dependencies separate from your operating system and record them in a requirements file or modern project configuration.
  2. Port old code deliberately. The original tutorial repository and the separate book repository are useful references. The book source identifies the finalized implementation under chapters/chapter_7.
  3. Validate at the API boundary. Reject unknown or malformed fields, enforce numeric ranges, and return consistent error responses.
  4. Define serialization. Specify canonical encoding before multiple implementations exchange blocks.
  5. Use appropriate HTTP methods. Preserve GET /mine when following the historical tutorial, but prefer POST /mine for new APIs.
  6. Add tests. Test hashing, proof validation, tampering, malformed requests, peer timeouts, and chain replacement.
  7. Add persistence carefully. A database can preserve state, but it does not solve consensus, identity, or adversarial networking.
  8. Add operational safeguards. Use structured logs, timeouts, authentication, rate limits, TLS, and concurrency controls in any service exposed beyond localhost.

Choosing the right next step

This tutorial is a good fit if you learn best by implementing data structures and want a compact introduction to blocks, hashing, proof of work, APIs, and basic reconciliation.

It is not enough if your goal is to:

  • Build a cryptocurrency.
  • Write secure wallets or signed transactions.
  • Develop Solidity applications or deploy smart contracts.
  • Operate a public peer-to-peer network.
  • Understand proof-of-stake systems in depth.
  • Design a production ledger or study Byzantine fault tolerance rigorously.

For a more complete cryptocurrency exercise, add signed transactions, wallets, a UTXO or account model, Merkle trees, persistent storage, peer-to-peer message propagation, difficulty adjustment, and explicit fork handling. For Ethereum development, follow a separate path covering Solidity, the EVM, local development networks, testing, deployment, and wallet signing. For consensus theory, study proof of work, proof of stake, Sybil resistance, finality, fork choice, safety, and liveness independently.

Bottom line

“Learn Blockchains by Building One” is still a strong conceptual exercise because it turns abstract terms into inspectable Python code. Build it to understand how hashes link blocks, how pending transactions enter a chain, how proof of work creates a computational puzzle, and how multiple nodes can compare histories.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Keep the security boundary clear: this project creates a toy blockchain network, not a secure financial asset. Hash chaining is not transaction authorization, mining is not economic security, and two local Flask processes are not a decentralized public network.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More post from the Money Desk

  1. The Money DeskBlogTheFinanceBase09 OCT 267 minMortgage Escrow FAQs: Taxes, Insurance, Shortages, and Refunds
  2. The Money DeskBlogTheFinanceBase09 OCT 265 minHow Mortgage Escrow Accounts Work and What Homeowners Pay For
  3. The Money DeskBlogTheFinanceBase09 OCT 265 minHow to Read a Stock Chart, Volume and Market-Cap Data
Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.