Blockchain 0x400 – Multi node Blockchains
Thilan Dissanayaka Web3 Development May 24, 2020

Blockchain 0x400 – Multi node Blockchains

So far, our blockchain runs on a single node — one machine holding all the blocks and transactions. That’s fine for learning, but it completely defeats the purpose of a decentralized ledger. Remember the whole point from our very first article? Alice, Bob, Trudy, and Eve each keep their own copy of the ledger. If only one person has the notebook, we’re back to a centralized system.

A real blockchain like Bitcoin or Ethereum runs on thousands of nodes, each keeping a copy of the ledger and syncing automatically. When Alice mines a block on her node, every other node in the network receives it and updates their chain.

In this tutorial, we’re going to make that happen. We’ll turn our single-machine blockchain into a multi-node distributed system. To achieve this, we need to build:

  • A network layer to connect multiple nodes.
  • A way to broadcast new blocks and transactions.
  • A consensus mechanism to keep everyone in sync when chains diverge.

We’ll keep things simple — nodes will communicate using HTTP (REST). You can evolve this into WebSockets or raw TCP later, but REST gives us a clean, easy-to-understand starting point.

The Node Concept

Before we write any code, let’s think about what each node needs to do:

  • Maintain its own copy of the blockchain.
  • Listen on a specific port for incoming requests.
  • Know about a few peers (other nodes in the network).
  • Share blocks and transactions with those peers.

Each node is essentially a small server running its own blockchain instance. When something happens on one node — a new transaction or a mined block — it broadcasts that event to all its peers. Those peers do the same, and the information propagates through the network.

Let’s build it.

Setting Up SparkJava

We’ll use SparkJava — a minimalistic Java HTTP framework that’s perfect for this kind of thing. No bloated Spring Boot, no XML config files. Just simple, clean routes.

Add this dependency to your build.gradle:

dependencies {
    implementation com.sparkjava:spark-core:2.9.4
    implementation com.google.code.gson:gson:2.10.1
}

Spark gives us the HTTP server, and Gson handles JSON serialization so our nodes can exchange data in a standard format.

The NodeServer Class

This is the heart of our networking layer. The NodeServer class wraps a blockchain instance and exposes it over HTTP:

import static spark.Spark.*;
import com.google.gson.Gson;
import java.util.ArrayList;
import java.util.List;

public class NodeServer {
    private Chain blockchain;
    private List<String> peers = new ArrayList<>();
    private Gson gson = new Gson();
    private int port;

    public NodeServer(int port) {
        this.port = port;
        this.blockchain = new Chain();
    }

    public void start() {
        port(port);

        // Get the full blockchain
        get("/chain", (req, res) -> {
            res.type("application/json");
            return gson.toJson(blockchain);
        });

        // Submit a new transaction
        post("/transaction", (req, res) -> {
            // Parse and add transaction to the pool
            // In a full implementation, you’d deserialize the transaction here
            res.type("application/json");
            return "{\"status\": \"Transaction added to pool\"}";
        });

        // Mine pending transactions
        post("/mine", (req, res) -> {
            blockchain.minePendingTransactions();
            broadcastChain();
            res.type("application/json");
            return "{\"status\": \"Block mined and broadcast\"}";
        });

        // Receive a chain from a peer
        post("/chain/update", (req, res) -> {
            // Handle incoming chain from peers
            res.type("application/json");
            return "{\"status\": \"Chain received\"}";
        });

        // Register a new peer
        post("/peers", (req, res) -> {
            String peerUrl = req.body();
            if (!peers.contains(peerUrl)) {
                peers.add(peerUrl);
            }
            res.type("application/json");
            return "{\"status\": \"Peer registered\"}";
        });

        // List all known peers
        get("/peers", (req, res) -> {
            res.type("application/json");
            return gson.toJson(peers);
        });

        System.out.println("Node started on port " + port);
    }
}

What’s going on here? Each node exposes a handful of REST endpoints:

  • GET /chain — returns the full blockchain as JSON. This is how other nodes can fetch your chain.
  • POST /transaction — accepts a new transaction and adds it to the pool.
  • POST /mine — mines all pending transactions into a new block, then broadcasts the updated chain to all peers.
  • POST /chain/update — receives a chain from a peer (used during synchronization).
  • GET /peers and POST /peers — manage the list of known peers.

The key method here is broadcastChain() — after mining a block, the node tells all its peers about the new chain.

Broadcasting to Peers

When a node mines a new block, it needs to tell everyone else. Here’s how we broadcast:

private void broadcastChain() {
    for (String peer : peers) {
        try {
            HttpClient client = HttpClient.newHttpClient();
            String chainJson = gson.toJson(blockchain);

            HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(peer + "/chain/update"))
                .header("Content-Type", "application/json")
                .POST(HttpRequest.BodyPublishers.ofString(chainJson))
                .build();

            client.send(request, HttpResponse.BodyHandlers.ofString());
            System.out.println("Broadcast to peer: " + peer);
        } catch (Exception e) {
            System.out.println("Failed to reach peer: " + peer);
        }
    }
}

Simple enough — we loop through all known peers and send them our full chain via an HTTP POST. If a peer is down, we catch the exception and move on. No crashing, no blocking.

In a production blockchain, you wouldn’t send the entire chain every time — you’d just broadcast the new block. But for our purposes, sending the full chain keeps things simple and sets us up nicely for the consensus mechanism.

Consensus — The Longest Chain Rule

Here’s where it gets interesting. What happens when two nodes mine a block at roughly the same time? Now you have two different versions of the chain floating around the network. Which one is correct?

This is the fork problem, and blockchain solves it with a simple rule: the longest valid chain wins.

The logic is straightforward — the longest chain represents the most computational work. If someone has a longer chain than yours, and it’s valid, you replace your chain with theirs.

Let’s add a resolveConflicts() method to our Chain class:

public boolean replaceChain(ArrayList<Block> newChain) {
    if (newChain.size() > blockchain.size() && isValidChain(newChain)) {
        System.out.println("Replacing chain with longer valid chain.");
        blockchain = newChain;
        return true;
    }
    return false;
}

private boolean isValidChain(ArrayList<Block> chain) {
    for (int i = 1; i < chain.size(); i++) {
        Block currentBlock = chain.get(i);
        Block previousBlock = chain.get(i - 1);

        if (!currentBlock.getHash().equals(currentBlock.calculateHash())) {
            return false;
        }
        if (!currentBlock.getPrevHash().equals(previousBlock.getHash())) {
            return false;
        }
    }
    return true;
}

Two checks happen before we accept a new chain:

  1. It must be longer than our current chain — if it’s the same length or shorter, we ignore it.
  2. It must be valid — every block’s hash must check out, and every link between blocks must be intact. We’re not going to blindly trust data from the network.

If both conditions are met, we swap out our chain. Otherwise, we keep ours. This is exactly how Bitcoin handles forks — eventually, one branch outpaces the other, and the entire network converges on a single truth.

Now we wire this into the /chain/update endpoint:

post("/chain/update", (req, res) -> {
    // Deserialize the incoming chain
    ArrayList<Block> receivedChain = deserializeChain(req.body());

    if (blockchain.replaceChain(receivedChain)) {
        res.type("application/json");
        return "{\"status\": \"Chain replaced\"}";
    } else {
        res.type("application/json");
        return "{\"status\": \"Chain kept — ours is longer or received chain is invalid\"}";
    }
});

Running Multiple Nodes

Time to see it all in action. Let’s update our Main class to spin up multiple nodes:

public class Main {
    public static void main(String[] args) {
        int port = Integer.parseInt(args[0]);

        NodeServer node = new NodeServer(port);
        node.start();
    }
}

Now you can launch multiple nodes in separate terminal windows:

# Terminal 1 — Node on port 4000
./gradlew run --args="4000"

# Terminal 2 — Node on port 4001
./gradlew run --args="4001"

# Terminal 3 — Node on port 4002
./gradlew run --args="4002"

Three nodes, each running their own blockchain. Now let’s connect them. Register peers using curl:

# Tell node 4000 about node 4001
curl -X POST http://localhost:4000/peers -d "http://localhost:4001"

# Tell node 4000 about node 4002
curl -X POST http://localhost:4000/peers -d "http://localhost:4002"

# Tell node 4001 about node 4000
curl -X POST http://localhost:4001/peers -d "http://localhost:4000"

Now when node 4000 mines a block, it broadcasts the chain to nodes 4001 and 4002. They validate it, and if it’s longer than theirs, they adopt it. The network stays in sync.

# Mine on node 4000
curl -X POST http://localhost:4000/mine

# Check chain on node 4001 — it should have the new block
curl http://localhost:4001/chain

That’s a distributed blockchain running on your local machine. Three independent nodes, communicating over HTTP, reaching consensus through the longest chain rule.

What’s Really Happening Under the Hood

Let’s trace through a full cycle to make sure the concept is clear:

  1. Alice submits a transaction to Node 4000.
  2. The transaction enters Node 4000’s transaction pool.
  3. Someone triggers mining on Node 4000 — POST /mine.
  4. Node 4000 mines a new block containing all pending transactions.
  5. Node 4000 broadcasts its updated chain to peers (4001 and 4002).
  6. Nodes 4001 and 4002 receive the chain, validate it, and — since it’s longer — replace their chains.
  7. All three nodes now have identical ledgers.

If Node 4001 also mined a block at the same time, creating a fork, the longest chain rule kicks in. Whichever chain grows faster (gets the next block first) wins, and the other nodes converge on it.

Wrapping Up

Look at how far we’ve come. We started with a simple linked list of blocks, and now we have:

  • Blocks with Proof of Work mining
  • Transactions with digital signatures
  • Wallets with public-private key pairs
  • Multiple nodes communicating over HTTP
  • Consensus via the longest chain rule

Our blockchain is no longer a toy running on one machine. It’s a distributed system where multiple independent nodes maintain the same truth — without trusting each other.

The full source code is available on GitHub: distributed-blockchain (branch 0x400)

There’s still a lot we could improve — peer discovery (so nodes don’t need manual registration), persistent storage (right now everything lives in memory), balance checking (preventing overdrafts), and mining rewards. But the foundation is solid. Every major concept that powers Bitcoin and Ethereum — hashing, mining, transactions, signatures, distributed consensus — we’ve built from scratch.

Not bad for four friends who just wanted to send money to each other.

ALSO READ
Blockchain 0x000 – Understanding the Fundamentals
May 21, 2020 Web3 Development

Imagine a world where strangers can exchange money, share data, or execute agreements without ever needing to trust a central authority. No banks, no intermediaries, no single point of failure yet...

Identity and Access Management (IAM)
May 11, 2020 Identity & Access Management

Who are you — and what are you allowed to do? That's the fundamental question every secure system must answer. And it's exactly what Identity and Access Management (IAM) is built to solve.

How I built a web based CPU Simulator
May 07, 2020 Pet Projects

As someone passionate about computer engineering, reverse engineering, and system internals, I've always been fascinated by what happens "under the hood" of a computer. This curiosity led me to...

Writing a Shell Code for Linux
Apr 21, 2020 Exploit Development

Shellcode is a small piece of machine code used as the payload in exploit development. In this post, we write Linux shellcode from scratch — starting with a simple exit, building up to spawning a shell, and explaining every decision along the way.

Exploiting a Stack Buffer Overflow on Windows
Apr 12, 2020 Exploit Development

In a previous tutorial we discusses how we can exploit a buffer overflow vulnerability on a Linux machine. I wen through all theories in depth and explained each step. Now today we are going to jump...

Access Control Models
Apr 08, 2020 Identity & Access Management

Access control is one of the most fundamental concepts in security. Every time you set file permissions, assign user roles, or restrict access to a resource, you're implementing some form of access control. But not all access control is created equal...

Exploiting a  Stack Buffer Overflow  on Linux
Apr 01, 2020 Exploit Development

Have you ever wondered how attackers gain control over remote servers? How do they just run some exploit and compromise a computer? If we dive into the actual context, there is no magic happening....

Basic concepts of Cryptography
Mar 01, 2020 Cryptography

Ever notice that little padlock icon in your browser's address bar? That's cryptography working silently in the background, protecting everything you do online. Whether you're sending an email,...

Common Web Application Attacks
Feb 05, 2020 Application Security

Web applications are one of the most targeted surfaces by attackers. This is primarily because they are accessible over the internet, making them exposed and potentially vulnerable. Since these...

Remote Code Execution (RCE)
Jan 02, 2020 Application Security

Remote Code Execution (RCE) is the holy grail of application security vulnerabilities. It allows an attacker to execute arbitrary code on a remote server — and the consequences are as bad as it sounds. In this post, we'll go deep into RCE across multiple languages, including PHP, Java, Python, and Node.js.