Building a Simple Gossip Protocol with Node.js and TypeScript

Building a Simple Gossip Protocol with Node.js and TypeScript

Introduction

Gossip protocols are a communication model used in distributed systems to disseminate information efficiently across a network without centralized coordination. These protocols are commonly found in large-scale systems like blockchain networks, databases, and P2P file-sharing systems due to their scalability, resilience, and reliability.

In this guide, you'll build a simple gossip protocol in TypeScript using Node.js. You’ll also extend the protocol to simulate the broadcasting of blockchain transactions, allowing each node to receive and propagate transactions to peers. By the end, you'll understand both the basic theory of gossip protocols and the implementation steps to build a functional gossip network.

Theoretical Background

What is a Gossip Protocol?

A gossip protocol is a communication model where nodes (or peers) in a network spread information by randomly sharing it with a few neighbors. This process is repeated until all nodes in the network receive the information. Like the concept of spreading a rumor, gossip protocols are resilient, eventually consistent, and adapt well to network changes.

Key Components of a Gossip Protocol

  1. Messages: The data exchanged between nodes. Messages may include a unique identifier to avoid duplication and a Time-To-Live (TTL) to limit propagation.

  2. Nodes: Each node is an individual process or instance in the network that can send, receive, and relay messages.

  3. Peer Selection: Nodes randomly select a subset of their peers to forward messages, creating a dynamic, non-centralized message flow.

  4. Propagation Rules: To prevent duplicate messages, nodes track received messages by their unique IDs and only forward messages that are new.

  5. Time-To-Live (TTL): Each message has a TTL to limit the number of hops it can take, helping control network congestion and prevent indefinite propagation.

  6. Resilience and Fault Tolerance: Gossip protocols are robust against node failures because other nodes continue to relay messages independently.


Building a Simple Gossip Protocol in Node.js and TypeScript

Overview of the Project

This guide will help you build a decentralized network where nodes can send and receive messages. Each node will have a unique port, enabling separate terminal instances to simulate the behavior of individual nodes. We'll also implement a simulated "transaction" to represent a blockchain transaction being broadcast through the gossip network.


Step 1: Set Up the Project

  1. Create a new project directory:

     mkdir gossip-protocol
     cd gossip-protocol
    
  2. Initialize a pnpm project and install dependencies:

     pnpm init
     pnpm add -D typescript tsx @types/node
    
  3. Configure TypeScript by creating a tsconfig.json file:

     {
       "compilerOptions": {
         "target": "es6",
         "module": "commonjs",
         "strict": true,
         "esModuleInterop": true,
         "outDir": "./dist"
       },
       "include": ["src/**/*"]
     }
    
  4. Set up the project structure by creating a src folder with two files:

     mkdir src
     touch src/gossipNode.ts src/index.ts
    

Step 2: Create the GossipNode Class

The GossipNode class handles each node's behavior, including receiving, processing, and gossiping messages to peers.

1. Define the Node’s Message Structure and Initial Imports

In src/gossipNode.ts:

import { createSocket, type Socket } from 'node:dgram';
import { randomUUID } from 'node:crypto';

interface Message {
    messageId: string;
    content: string;
    ttl: number;
}

2. Define the GossipNode Class Structure

The GossipNode class is where the core gossip protocol logic resides. Each node will:

  • Receive incoming messages

  • Track messages to avoid duplication

  • Gossip messages to a random subset of peers

class GossipNode {
    private socket: Socket;
    private receivedMessages: Set<string> = new Set();

    constructor(private port: number, private peers: { ip: string; port: number }[]) {
        this.socket = createSocket('udp4');
    }

    start() {
        this.socket.on('message', (msg, rinfo) => {
            const message: Message = JSON.parse(msg.toString());
            this.handleMessage(message);
        });

        this.socket.bind(this.port, () => {
            console.log(`Node listening on port ${this.port}`);
        });
    }

    private handleMessage(message: Message) {
        if (!this.receivedMessages.has(message.messageId) && message.ttl > 0) {
            console.log(`Node ${this.port} received message: ${message.content}`);
            this.receivedMessages.add(message.messageId);
            this.gossip({ ...message, ttl: message.ttl - 1 });
        }
    }

    sendMessage(content: string) {
        const messageId = randomUUID();
        const message: Message = { messageId, content, ttl: 3 };
        this.receivedMessages.add(messageId);
        this.gossip(message);
    }

    private gossip(message: Message) {
        const peersToSend = this.getRandomPeers(2);
        peersToSend.forEach((peer) => {
            this.socket.send(
                JSON.stringify(message),
                peer.port,
                peer.ip,
                (error) => {
                    if (error) {
                        console.error(`Error sending to ${peer.ip}:${peer.port}`);
                    }
                }
            );
        });
    }

    private getRandomPeers(count: number) {
        return this.peers
            .sort(() => 0.5 - Math.random())
            .slice(0, Math.min(count, this.peers.length));
    }
}

export default GossipNode;

Step 3: Set Up Nodes and Simulate a Transaction Broadcast

To simulate a network where nodes gossip a "transaction" message, we’ll set up nodes in src/index.ts.

import GossipNode from "./gossipNode";

// Define nodes and peers for a small network
const nodes = [
    { ip: '127.0.0.1', port: 5001 },
    { ip: '127.0.0.1', port: 5002 },
    { ip: '127.0.0.1', port: 5003 },
    { ip: '127.0.0.1', port: 5004 },
];

// Initialize a GossipNode instance
const nodeId = parseInt(process.argv[2] || '0');
const { ip, port } = nodes[nodeId];
const peers = nodes.filter((node) => node.port !== port);

const gossipNode = new GossipNode(port, peers);
gossipNode.start();

// Simulate a "transaction" broadcast from the first node
if (nodeId === 0) {
    setTimeout(() => {
        gossipNode.sendMessage("🔗 New transaction: 0x123abc");
    }, 2000); // Wait a bit for other nodes to start listening
}

Step 4: Run the Gossip Nodes

  1. Open multiple terminals, one for each node, and start each node by specifying its ID as a command-line argument:

     pnpm tsx src/index.ts 1
     pnpm tsx src/index.ts 2
     pnpm tsx src/index.ts 3
     # Start Node 0 last, so it can send the transaction message when others are ready
     pnpm tsx src/index.ts 0
    
  2. The first node (Node 0) will initiate a simulated transaction message. Each node will propagate the message to a random subset of its peers until all nodes have received the message.


Key Concepts Recap

  • Unique Transaction ID: Each "transaction" has a unique message ID to prevent duplicates.

  • Peer Gossiping: Nodes randomly select peers to send messages, allowing for efficient dissemination.

  • Transaction TTL: A ttl property limits message propagation to avoid network congestion.


Conclusion

You now have a functioning gossip protocol that simulates the spread of a blockchain transaction. This setup showcases the decentralized, resilient nature of gossip networks, which are essential to maintaining data consistency in distributed systems like blockchains.

Further Reading and Research Resources

To deepen understanding and explore real-world implementations of enhanced gossip protocols in blockchain systems, consider the following materials:

  • Articles:

  • Books and Tutorials:

    • Distributed Systems: Principles and Paradigms by Andrew S. Tanenbaum and Maarten Van Steen – Covers core distributed system principles, including gossip protocols and fault tolerance.
  • Blockchain-Specific Implementations:

    • LibP2P (used by IPFS and Ethereum 2.0) – A modular network stack that implements gossiping with peer-to-peer components, allowing exploration of real-world blockchain gossiping mechanisms.