Building Layer 1 blockchain from scratch (PART — IV P2P)

Abhiveer Singh
4 min readAug 15, 2024

--

Flight to Dehradun

This part is in continuation of part 3

In the previous article, we integrated LevelDB and Merkle Trees to manage and verify our blockchain’s data. This setup allowed us to handle account states and transactions securely, laying the foundation for the next crucial step: enabling peer-to-peer (P2P) networking.

In this final part, we will implement P2P communication using IPFS (InterPlanetary File System) to allow nodes to communicate in a decentralized manner. This will complete our Layer 1 blockchain by enabling it to operate in a fully decentralized network.

Environment Setup

Before running the code, ensure you have the following setup:

  • Python 3.x: Make sure Python is installed and properly configured.
  • IPFS: Install and initialize IPFS on your machine. You can install IPFS using the following commands:
wget https://dist.ipfs.tech/go-ipfs/v0.12.2/go-ipfs_v0.12.2_linux-amd64.tar.gz
tar -xvzf go-ipfs_v0.12.2_linux-amd64.tar.gz
sudo bash install.sh
ipfs init
ipfs daemon
  • Threading: Ensure you are familiar with threading in Python, as we will run multiple threads for publishing and subscribing to messages.:

Python Version: 3.11.6

Operating System: Linux Ubuntu

Database: LevelDB (from the previous part)

Refer to code here

Code

1. Importing Required Modules

import subprocess
import time
import threading
import uuid
  • Imports: We import necessary libraries such as subprocess to execute IPFS commands, threading to handle concurrent tasks, and uuid to generate unique identifiers for each node.

2. Initializing IPFS class

class IPFSClient:
def __init__(self, topic):
self.topic = topic
self.id = str(uuid.uuid4()) # Unique identifier for each instance
  • Class Initialization: The IPFSClient class manages the P2P communication. Each instance is identified by a unique UUID, which is useful for distinguishing between different nodes in the network.

3. Subscribe function

def subscribe(self):
# Use subprocess to run the IPFS CLI command to subscribe to the topic
process = subprocess.Popen(
["ipfs", "pubsub", "sub", self.topic],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
print(f"Subscribed to topic: {self.topic}")

# Continuously listen to messages from the topic
while True:
print("Waiting for message")
time.sleep(5)
output = process.stdout.readline()
if output:
print(f"Received message: {output}")

Subscribe Function:

  • Topic Subscription: This function subscribes to a specified IPFS topic using the ipfs pubsub sub command.
  • Listening for Messages: It continuously listens for incoming messages on the topic and prints any received messages to the console.
  • Sleep Interval: Similar to publishing, a sleep interval is used to prevent high CPU usage.

4. Publish function

def publish(self, message):
while True:
# Create a message with the unique ID
# message = f"Message from {self.id}: Hello from IPFS!"
message = message

# Use subprocess to run the IPFS CLI command to publish the message
process = subprocess.run(
["ipfs", "pubsub", "pub", self.topic, message],
capture_output=True,
text=True
)

if process.returncode == 0:
print(f"Published message: {message}")
else:
print(f"Failed to publish message: {message}, Error: {process.stderr}")

# Sleep before the next publish
time.sleep(5)

Publish Function:

  • Message Publishing: This function continuously publishes messages to a specified IPFS topic. The message is sent using the IPFS CLI command ipfs pubsub pub.
  • Error Handling: If the message fails to publish, the error is captured and printed.
  • Sleep Interval: A sleep interval of 5 seconds is used to control the frequency of message publishing.

5.Main “run” function

def run(self):
try:
# Start both publishing and subscribing in parallel using threads
publish_thread = threading.Thread(target=self.publish)
subscribe_thread = threading.Thread(target=self.subscribe)

publish_thread.start()
subscribe_thread.start()

# Keep the main thread alive while the threads run
publish_thread.join()
subscribe_thread.join()

except KeyboardInterrupt:
print("Interrupted by user. Exiting...")
except Exception as e:
print(f"An error occurred: {e}")

Run Function:

  • Parallel Execution: This function starts the publish and subscribe methods in separate threads, allowing them to run concurrently.
  • Thread Management: The join() method ensures that the main thread waits for the publishing and subscribing threads to complete before exiting.
  • Error Handling: It gracefully handles user interruptions and other exceptions.
if __name__ == "__main__":
ipfs_client = IPFSClient(topic="example_topic")
ipfs_client.run()
  • Main Execution: The script initializes an IPFSClient instance with a specific topic and starts the P2P communication by calling the run() method.

🏁 Conclusion

In this final part of our series, we implemented P2P networking using IPFS, enabling decentralized communication between blockchain nodes. By integrating P2P networking, our Layer 1 blockchain can now operate in a fully decentralized environment, where nodes can share data and maintain consensus across the network.

With the completion of this P2P implementation, we now have a fully functional Layer 1 blockchain built from scratch in Python, complete with transaction handling, data persistence, and decentralized communication.

In the next articles, I’ll simplify zero-knowledge proofs and their role in blockchain technology. We’ll explore how they enhance privacy and security, with practical examples of their implementation. Stay tuned!

👋 👋 👋

--

--

Abhiveer Singh

11th Grader | Advancing LLM Security & Agentic Models | Researching Zero-Knowledge Proofs with R1CS & KZG Commitments https://www.linkedin.com/in/abhiveerhome/