Blockchain 0x200 – Introducing Transactions to the Core Blockchain
In the last tutorial, we built a functioning blockchain from scratch — blocks, hashing, mining, chain validation, the whole thing. But if you remember, our blocks were storing simple strings as data. That’s fine for a demo, but it’s not how real blockchains work.
Think back to Alice, Bob, Trudy, and Eve. Their whole goal was to build a banking system — to send money to each other and keep track of transactions. Right now, our blockchain can’t do that. It just stores arbitrary text.
So in this tutorial, we’re going to fix that. We’ll extend our blockchain to hold actual transactions — who sent what to whom, and how much. Let’s get into it.
What We Need
To make our blockchain transaction-aware, we need a few things:
- A Transaction class that represents actual transfers between users.
- A modified Block class that holds multiple transactions instead of a single string.
- A TransactionPool to temporarily store unconfirmed transactions before they’re mined.
- An updated Chain class that mines blocks containing transaction data.

Let’s build each one.
The Transaction Class
Let’s start with a very simple transaction model. Each transaction records three things:
- from — the sender
- to — the receiver
- amount — how much is being transferred
In a later part, we’ll introduce proper cryptographic addresses for users. In that case, these sender and receiver fields will be replaced by their specific addresses. For now, let’s keep it simple and use strings to represent their names.
public class Transaction {
private String from;
private String to;
private double amount;
public Transaction(String from, String to, double amount) {
this.from = from;
this.to = to;
this.amount = amount;
}
@Override
public String toString() {
return from + " -> " + to + ": " + amount;
}
}
Pretty self-explanatory, right? No big deal here. We’ve got a constructor that sets the sender, receiver, and amount, plus a toString() method that gives us a readable representation like Alice -> Bob: 10.0.
It’s a minimal structure, but good enough for our purposes. Later, we’ll upgrade this with digital signatures to prove ownership and prevent tampering — but one step at a time.
Modifying the Block Class
In our old blockchain, each block stored a single string like "Alice sends $5 to Bob". Now, we’ll replace that with a list of Transaction objects.
The key change is in the properties. Instead of:
private String data;
We now have:
private ArrayList<Transaction> transactions;
And the constructor changes accordingly — it now accepts a list of transactions instead of a string:
public Block(int index, ArrayList<Transaction> transactions, int difficulty, String prevHash) {
this.transactions = transactions;
this.difficulty = difficulty;
this.prevHash = prevHash;
this.timestamp = Instant.now().toEpochMilli();
mineBlock();
}
The other big change is in our calculateHash() method. We need to convert all transactions into a string before hashing:
public String calculateHash() {
String data = "";
for (Transaction tx : transactions) {
data += tx.toString();
}
String dataToHash = data + nonce + difficulty + timestamp + prevHash;
this.hash = CryptoUtils.applySha256(dataToHash);
return hash;
}
What’s happening? We’re iterating through all the transactions in the block, concatenating their string representations, and then hashing the whole thing along with the nonce, difficulty, timestamp, and previous hash — same as before, just with richer data.
Each block now contains a list of transactions, which are all hashed and secured during mining. If anyone tries to change even one transaction amount or swap a sender’s name, the hash breaks.
Transaction Pool
Now here’s where it gets interesting.
In our blockchain so far, transactions go directly into a block the moment they’re created. But that’s not how real blockchains work. In a real network like Bitcoin or Ethereum, transactions first enter a temporary storage area called the transaction pool (or mempool) before being added to a block.

This pool stores all unconfirmed transactions — those that have been broadcast to the network but not yet mined into a block. When a miner creates a new block, they pick transactions from this pool, verify them, and include them in the block before mining.
Why Do We Need a Transaction Pool?
Let’s consider the lifecycle of a transaction:
- Alice sends 10 coins to Bob.
- The transaction is created and broadcast to the network.
- All nodes receive the transaction and store it in their transaction pool.
- A miner selects transactions from the pool to include in the next block.
- Once mined, those transactions are removed from the pool — they’re now confirmed and permanently recorded on the chain.
Without a transaction pool, you’d have to mine a new block for every single transaction. That’s wildly inefficient. The pool lets transactions accumulate, and then a miner batches them together into a single block. This is exactly how Bitcoin works — each block contains hundreds or even thousands of transactions.
This design also allows the blockchain to behave like a decentralized, asynchronous network, where many pending transactions can exist before they’re confirmed.
The TransactionPool Class
Let’s create a new file called TransactionPool.java:
public class TransactionPool {
private ArrayList<Transaction> pendingTransactions = new ArrayList<>();
public void addTransaction(Transaction tx) {
pendingTransactions.add(tx);
}
public ArrayList<Transaction> getPendingTransactions() {
return pendingTransactions;
}
public void clear() {
pendingTransactions.clear();
}
}
Three simple methods — addTransaction() to queue up a new transaction, getPendingTransactions() to grab the current batch, and clear() to flush the pool after mining. Clean and straightforward.
Updating the Chain Class
Now we need to wire the transaction pool into our Chain class. The chain needs to know about the pool, and it needs a new method to mine all pending transactions into a block.
Here’s the updated Chain.java:
public class Chain {
private final int difficulty = 4;
private final ArrayList<Block> blockchain = new ArrayList<>();
private TransactionPool pool = new TransactionPool();
public Chain() {
ArrayList<Transaction> genesisTransactions = new ArrayList<>();
genesisTransactions.add(new Transaction("system", "genesis", 0));
blockchain.add(new Block(0, genesisTransactions, difficulty, "0"));
}
public void addTransaction(String from, String to, double amount) {
pool.addTransaction(new Transaction(from, to, amount));
}
public void minePendingTransactions() {
Block block = new Block(
blockchain.size(),
pool.getPendingTransactions(),
difficulty,
getLatestBlock().getHash()
);
blockchain.add(block);
pool.clear();
}
public Block getLatestBlock() {
return blockchain.get(blockchain.size() - 1);
}
// ... isChainValid() and printChain() remain the same
}
What changed? A few things:
- The chain now holds a
TransactionPoolinstance. addTransaction()is a convenience method — instead of creatingTransactionobjects externally, we just pass the sender, receiver, and amount.minePendingTransactions()grabs everything from the pool, packs it into a new block, mines it, adds it to the chain, and then clears the pool.- The Genesis Block now contains a dummy transaction instead of a plain string.
The flow is now: add transactions → mine pending → repeat. Just like a real blockchain.
Putting It All Together
Let’s see our transaction-aware blockchain in action:
public class Main {
public static void main(String[] args) {
Chain blockchain = new Chain();
// Add some transactions
blockchain.addTransaction("Alice", "Bob", 10);
blockchain.addTransaction("Bob", "Trudy", 5);
blockchain.addTransaction("Trudy", "Eve", 3);
// Mine the pending transactions into a block
blockchain.minePendingTransactions();
// Add more transactions
blockchain.addTransaction("Eve", "Alice", 7);
blockchain.addTransaction("Alice", "Trudy", 2);
// Mine again
blockchain.minePendingTransactions();
blockchain.printChain();
}
}
When you run this, three blocks get mined — the Genesis Block plus two blocks containing our transactions. Each block batches multiple transactions together, just like Bitcoin does. Alice sends to Bob, Bob sends to Trudy, Trudy sends to Eve — all recorded immutably on the chain.
Wrapping Up
Let’s look at what we’ve accomplished in this tutorial:
- Transaction class — represents a transfer between two parties
- Updated Block class — now holds a list of transactions instead of a plain string
- TransactionPool — temporarily stores unconfirmed transactions before mining
- Updated Chain class — mines pending transactions into blocks in batches
Our blockchain is starting to look like something real. We can create transactions between users, batch them into blocks, and mine them into the chain. That’s a massive step up from storing arbitrary strings.
The full source code is available on GitHub: distributed-blockchain (branch 0x200)
But here’s the elephant in the room — there’s nothing stopping Trudy from creating a transaction that says "Alice -> Trudy: 1000". Anyone can fake a sender right now. That’s a pretty big security hole. In the next article, we’ll fix this by implementing digital signatures and cryptographic wallets. Each user will have a public-private key pair, and only the owner of a private key can authorize transactions from their account. Things are about to get cryptographic.