Sem 7

Minichain Project

Lab deck 5 and the Minichain Flask app: transactions and the mempool, block structure, SHA-256 hashing, chaining with previous_hash, the genesis block, simplified proof of work, chain validation, the Flask routes, and blockchain network families, types and layers.

Building a conceptual blockchainSearch on YouTube

The deck builds a toy blockchain in Python, called Minichain, to answer five questions:

  1. What is a blockchain?
  2. What is a transaction?
  3. What makes a block?
  4. How are blocks connected?
  5. When a transaction happens, where does it go? Directly to the block?

The code is in two files: blockchain.py (the Block and Blockchain classes) and app.py (a Flask web API with an HTML front end). The only dependency is Flask==3.0.3.

pip install -r requirements.txt     # Flask==3.0.3
python app.py                       # serves on http://0.0.0.0:5000

TerminologiesSearch on YouTube

  • Blockchain: a distributed ledger.
  • Transactions: records. "A transaction is simply a record describing a proposed state change or transfer."

TransactionsSearch on YouTube

def add_transaction(self, sender, receiver, amount):
    transaction = {
        "sender": sender,
        "receiver": receiver,
        "amount": amount
    }
    self.pending_transactions.append(transaction)
    return transaction

A transaction here is a Python dict with three fields. It is not written to a block. It is appended to a waiting list.

Mem poolSearch on YouTube

self.pending_transactions = []

pending_transactions plays the role of the mempool (memory pool). A transaction is created first. It is not immediately part of the blockchain. It waits in the mempool until a block is mined.

What is a block?Search on YouTube

class Block:
    def __init__(self, index, timestamp, transactions,
                 previous_hash, nonce=0):
        self.index = index
        self.timestamp = timestamp
        self.transactions = transactions
        self.previous_hash = previous_hash
        self.nonce = nonce
        self.hash = self.calculate_hash()
FieldMeaning
indexPosition of the block in the chain (genesis = 0)
timestampCreation time from time() (Unix seconds)
transactionsList of transaction dicts taken from the mempool
previous_hashHash of the block before this one
nonceNumber changed during mining; starts at 0
hashSHA-256 of all the fields above

Hashing a blockSearch on YouTube

def calculate_hash(self):
    block_data = {
        "index": self.index,
        "timestamp": self.timestamp,
        "transactions": self.transactions,
        "previous_hash": self.previous_hash,
        "nonce": self.nonce
    }
    encoded_block = json.dumps(block_data, sort_keys=True).encode()
    return hashlib.sha256(encoded_block).hexdigest()

Steps:

  1. Put every field except hash in a dict.
  2. Serialise it to JSON with sort_keys=True. Sorting keys makes the string, and so the hash, the same every time for the same data.
  3. .encode() turns the string into bytes.
  4. hashlib.sha256(...).hexdigest() returns a 64-character hex string (256 bits).

The hash is a fingerprint of the block. Change one character of one transaction and the hash changes completely.

Connecting the blocksSearch on YouTube

previous_hash=self.get_latest_block().hash

When creating a new block, we take the hash of the previous block and store it inside the new block. get_latest_block() returns self.chain[-1]. Because previous_hash is part of the hashed data, each block's hash depends on every block before it.

Genesis blockSearch on YouTube

If every block points to a previous block, where does the chain start?

def create_genesis_block(self):
    genesis_block = Block(
        index=0,
        timestamp=time(),
        transactions=[],
        previous_hash="0"
    )
    self.chain.append(genesis_block)

The genesis block is block 0. It has no transactions and a placeholder previous_hash of "0". The Blockchain constructor creates it automatically:

class Blockchain:
    def __init__(self):
        self.chain = []
        self.pending_transactions = []
        self.difficulty = 3
        self.create_genesis_block()

Pending transactions and block creationSearch on YouTube

self.pending_transactions.append(transaction)          # when a tx arrives
transactions=self.pending_transactions.copy()          # when a block is built

The new block gets a copy of the mempool. After mining, the mempool is emptied (self.pending_transactions = []). Copying matters: clearing the list later must not empty the block's own list.

Who can add the block to the blockchain?Search on YouTube

Anyone could build a block. A blockchain needs a mechanism that makes creating a valid block require computational work. That mechanism is Proof of Work.

Proof of WorkSearch on YouTube

self.difficulty = 3
target = "0" * self.difficulty      # "000"

A block is valid only if its hash starts with difficulty zeros. With difficulty 3, the hash must start with "000".

NonceSearch on YouTube

In the simplified Proof of Work, the nonce is a value we keep changing to generate different hashes.

def mine_pending_transactions(self):
    if len(self.pending_transactions) == 0:
        return None

    new_block = Block(
        index=len(self.chain),
        timestamp=time(),
        transactions=self.pending_transactions.copy(),
        previous_hash=self.get_latest_block().hash
    )

    target = "0" * self.difficulty
    while not new_block.hash.startswith(target):
        new_block.nonce += 1
        new_block.hash = new_block.calculate_hash()

    self.chain.append(new_block)
    self.pending_transactions = []
    return new_block

The mining flow from the deck:

Pending txsCreate blockAdd prev hashnonce = 0Calculate hashstarts with"000"?nonce += 1Add blockNoYes

Mining is hard (many hash attempts). Checking is easy (one hash). This asymmetry is the core of Proof of Work. Each extra zero of difficulty makes mining about 16 times harder, since each hex digit has 16 values.

Validation of the blockchainSearch on YouTube

def is_chain_valid(self):
    for i in range(1, len(self.chain)):
        current_block = self.chain[i]
        previous_block = self.chain[i - 1]

        if current_block.hash != current_block.calculate_hash():
            return False
        if current_block.previous_hash != previous_block.hash:
            return False
        if not current_block.hash.startswith("0" * self.difficulty):
            return False
    return True

Three checks for every block after genesis:

CheckCatches
Stored hash equals recomputed hashData inside the block was edited
previous_hash equals the previous block's hashThe link between blocks was broken
Hash starts with the target zerosA hash was replaced without doing the work

Hash, Link, Work: "Honest Ledgers Win".

Flask routesSearch on YouTube

app.py wraps one Blockchain() object in a Flask app:

app = Flask(__name__)
blockchain = Blockchain()
RouteMethodWhat it doesStatus codes
/GETRenders templates/index.html (the UI)200
/transactionsPOSTAdds a tx to the mempool from JSON sender, receiver, amount201; 400 if a field is missing
/minePOSTMines the pending txs into a new block200; 400 "No pending transactions to mine"
/chainGETReturns length and the full chain200
/pendingGETReturns the mempool200
/validateGETReturns {"valid": true/false} from is_chain_valid()200
/tamperPOSTEdits the first tx (and optionally the hash) of a block, for demo200; 400 or 404 on bad input
@app.route("/transactions", methods=["POST"])
def create_transaction():
    data = request.get_json()
    sender = data.get("sender")
    receiver = data.get("receiver")
    amount = data.get("amount")
    if not sender or not receiver or amount is None:
        return jsonify({"error": "Sender, receiver and amount are required"}), 400
    transaction = blockchain.add_transaction(sender, receiver, amount)
    return jsonify({
        "message": "Transaction added successfully",
        "transaction": transaction,
        "pending_transactions": len(blockchain.pending_transactions)
    }), 201

The /tamper route refuses the genesis block (index 0), a block index that does not exist, and a block with no transactions. It changes sender, receiver or amount of the first transaction. It can also overwrite block.hash directly.

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)

Trying the APISearch on YouTube

curl -X POST localhost:5000/transactions -H "Content-Type: application/json" \
     -d '{"sender":"Alice","receiver":"Bob","amount":10}'
curl localhost:5000/pending
curl -X POST localhost:5000/mine
curl localhost:5000/chain
curl localhost:5000/validate                    # {"valid": true}
curl -X POST localhost:5000/tamper -H "Content-Type: application/json" \
     -d '{"block_index":1,"amount":1000}'
curl localhost:5000/validate                    # {"valid": false}

What to do and check in the lab:

  1. Install Flask and run python app.py; open http://localhost:5000.
  2. Use "Create Transaction" a few times, then look at the pending list.
  3. Click "Mine" and see the nonce and a hash starting with 000.
  4. View the chain: check each block's previous_hash equals the hash of the block before.
  5. "Validate" gives valid. Use "Tamper Block" to change an amount, then validate again: invalid, because the recomputed hash differs.
  6. Tamper by setting a new hash too: the next block's previous_hash link now breaks.

Now the real game: blockchain networksSearch on YouTube

Major families of blockchain networksSearch on YouTube

NetworkNative assetBroad purpose
BitcoinBTCDigital money / settlement
EthereumETHSmart contracts / general-purpose blockchain
SolanaSOLHigh-throughput smart-contract platform
CardanoADASmart-contract platform
AvalancheAVAXSmart contracts / customizable networks
PolkadotDOTInteroperability / multi-chain ecosystem
Cosmos ecosystemATOM + individual chain tokensInteroperability / application-specific chains
XRP LedgerXRPPayments / settlement
StellarXLMPayments / asset transfer
SuiSUISmart-contract platform
AptosAPTSmart-contract platform

Types of blockchainSearch on YouTube

TypeDefinitionExamples
PublicOpen, permissionless network anyone can join, read or write. No central authority; a distributed network validates transactions.Bitcoin (original P2P digital currency), Ethereum (smart contracts and dApps), Litecoin (faster alternative to Bitcoin)
PrivateRestricted network controlled by one organisation. Only approved participants access or validate. Faster and more centralised than public.Hyperledger Fabric (permissioned chains for internal operations), Corda by R3 (private transaction processing for financial institutions), MultiChain (enterprise supply chain or data sharing)
Consortium (federated)Governed by a group of organisations. Several pre-selected entities share validation. Common in banking and supply chains.Quorum (JPMorgan, interbank), Energy Web Chain (energy companies, grid and energy trading), We.trade (trade finance, European banks)
HybridMix of public and private: some data stays private in an organisation, some is public or verifiable.XinFin XDC Network (trade finance and supply chain), Dragonchain (built by Disney; anchors proofs to a public chain), IBM Food Trust (supply-chain tracking)

Mainnet vs testnetSearch on YouTube

Mainnet is the live production network where real transactions occur and tokens have real value (for example Bitcoin's mainnet). Testnet is a separate parallel network for testing. It mimics mainnet but uses worthless test tokens, often free from a faucet. Developers deploy contracts and catch bugs without risking real money.

AspectMainnetTestnet
PurposeReal-world useDevelopment and testing
Token valueReal, has monetary valueFake, no monetary value
StabilityStable, final rulesFrequently reset or updated
UsersGeneral public, businessesDevelopers, testers
ExampleEthereum MainnetSepolia, Goerli (Ethereum testnets)

Layer 1, Layer 2 and overlay networksSearch on YouTube

Layer 1 (L1) is the base blockchain where transactions are permanently recorded and verified by everyone. Examples: Bitcoin, Ethereum. It confirms and secures every transaction and keeps a permanent, tamper-proof record. It depends on no other network; it is the source of truth. The catch: the whole network checks every transaction, so L1s are slow and expensive under heavy use.

Layer 2 (L2) is a separate system on top of an L1. It handles transactions faster and cheaper but relies on the L1 for final security. Examples: Lightning Network (on Bitcoin), Arbitrum and Polygon (on Ethereum). How it works:

  1. Users do many transactions on the L2, fast and nearly free.
  2. The L2 periodically bundles them and sends a short summary to the L1.
  3. The L1 checks and locks in the summary, making it official and secure.

An overlay network runs on top of an existing blockchain but mainly adds a new feature or use case, not speed. Examples on Bitcoin: Lightning Network (also called an overlay; instant micropayments), Ordinals / BRC-20 (tokens and NFT-like functions), Counterparty (asset trading). Key idea: an overlay does not change the base chain. It uses the L1 as a foundation and adds rules that only overlay-aware software interprets.

Quick self-testSearch on YouTube

Viva: when a user submits a transaction in Minichain, does it go straight into a block?

No. add_transaction appends it to pending_transactions, the mempool. It enters a block only when /mine runs mine_pending_transactions.

List the fields of a Minichain block and say which are hashed.

index, timestamp, transactions, previous_hash, nonce and hash. All except hash itself go into the SHA-256 input.

Why does calculate_hash use json.dumps(..., sort_keys=True)?

To get the same string, and so the same hash, for the same data every time. Without sorted keys, key order could change the bytes and the hash.

What is the genesis block and what is its previous_hash?

The first block (index 0), created in the Blockchain constructor. It has no transactions and previous_hash = "0" because no earlier block exists.

Viva: what does the nonce do in Minichain's Proof of Work?

It is a counter that changes the block data so each attempt gives a new hash. Mining increments it until the hash starts with "0" * difficulty ("000").

Why is pending_transactions.copy() used when creating a block?

The mempool is cleared after mining. The copy keeps the block's own list intact when pending_transactions is reset.

Name the three checks in is_chain_valid.

The stored hash equals the recomputed hash. previous_hash equals the previous block's hash. The hash starts with the difficulty zeros.

Viva: you tamper with the amount in block 1. Which check fails, and why?

The first check. The stored hash was computed from the old data, so recomputing from the edited data gives a different hash.

Which Flask route returns HTTP 201, and which returns 400 when there is nothing to mine?

POST /transactions returns 201 on success. POST /mine returns 400 with "No pending transactions to mine".

Distinguish public, private, consortium and hybrid blockchains with one example each.

Public: open and permissionless (Bitcoin). Private: one organisation controls access (Hyperledger Fabric). Consortium: a group of organisations shares validation (Quorum). Hybrid: some data private, some public (XinFin XDC).

How does a Layer 2 differ from an overlay network?

An L2 aims to make transactions faster and cheaper and settles summaries on the L1. An overlay mainly adds a new feature, such as tokens via Ordinals or BRC-20, without changing the base chain.

  • A transaction is a record of a proposed transfer; it waits in the mempool (pending_transactions) until mined.
  • A block holds index, timestamp, transactions, previous_hash, nonce and a SHA-256 hash of the rest.
  • Blocks link through previous_hash; the genesis block has index 0, no transactions and previous_hash "0".
  • Proof of Work: increment the nonce until the hash starts with "000" (difficulty 3); hard to find, easy to check.
  • Validation checks hash, link and work for every block; any tampering breaks one of them.
  • Flask routes: /transactions, /mine, /chain, /pending, /validate, /tamper on port 5000.
  • Chain types: public, private, consortium, hybrid. Mainnet has real value; testnets like Sepolia use faucet tokens.
  • L1 is the base chain; L2 adds speed and settles on L1; overlays add features without changing L1.