Distributed Blockchain Network (C++)
A proof-of-work blockchain in C++ featuring SHA-256 mining, transaction validation, and peer-to-peer networking with automatic distributed consensus.
A deep dive into implementing a proof-of-work blockchain with peer-to-peer networking
Introduction
Ever wondered how blockchains actually work under the hood? I spent the past week building a fully functional distributed blockchain from scratch in C++.
In this write-up, I’ll walk through the implementation, explain the key concepts, and share what I learned about distributed systems, cryptography, and network programming.
View the full project on GitHub
What I Built
A complete blockchain system featuring:
- ✅ Proof-of-work mining with SHA-256
- ✅ Transaction validation and balance tracking
- ✅ TCP-based peer-to-peer networking
- ✅ Multi-threaded architecture
- ✅ Automatic chain synchronization
- ✅ JSON persistence
Part 1: Understanding Blockchains
What is a Blockchain?
At its core, a blockchain is deceptively simple: a linked list where each node contains a cryptographic hash of the previous node.
Block 0 (Genesis) Block 1 Block 2
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Index: 0 │ │ Index: 1 │ │ Index: 2 │
│ PrevHash: "0" │◄───│ PrevHash: 00a3..│◄───│ PrevHash: 00f7..│
│ Hash: 00a3f5... │ │ Hash: 00f7d2... │ │ Hash: 0012ab... │
│ Data: [txs] │ │ Data: [txs] │ │ Data: [txs] │
│ Nonce: 47283 │ │ Nonce: 19384 │ │ Nonce: 82947 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Change anything in Block 1? Its hash changes. But Block 2 stores Block 1’s old hash, so the link breaks. The chain is tamper-evident.
Why is This Secure?
Cryptographic hashing makes it computationally infeasible to:
- Find two inputs that produce the same hash (collision resistance)
- Reverse a hash to get the original input (preimage resistance)
- Modify data without detection (tamper evidence)
Part 2: Core Implementation
SHA-256: The Foundation
Every block’s identity is its SHA-256 hash—a 256-bit fingerprint calculated from all its data:
std::string Block::calculateHash() const {
// Combine all block data
std::string data =
std::to_string(index) +
std::to_string(timestamp) +
std::to_string(nonce) +
transactionsAsString() +
previousHash;
// SHA-256 hash
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256_CTX sha256;
SHA256_Init(&sha256);
SHA256_Update(&sha256, toHash.c_str(), toHash.size());
SHA256_Final(hash, &sha256);
// Convert to hex string
std::stringstream ss;
for(int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i];
}
return ss.str();
}
Change even one character in the input, and you get a completely different hash. This is what makes tampering detectable.
Proof-of-Work: Making Mining Expensive
Here’s the problem: if anyone can create blocks instantly, the chain is worthless. Solution? Make block creation computationally expensive.
The Challenge: Find a hash that starts with a certain number of zeros.
void Block::mineBlock(int difficulty) {
std::string target(difficulty, '0'); // e.g., "0000"
while (hash.substr(0, difficulty) != target) {
nonce++; // Try different nonces
hash = calculateHash();
}
std::cout << "Block mined! Hash: " << hash << std::endl;
}
Why this works:
- SHA-256 output is uniformly random
- ~1 in 16 hashes starts with “0”
- ~1 in 256 starts with “00”
- ~1 in 4,096 starts with “000”
- ~1 in 65,536 starts with “0000”
Difficulty 4 means trying ~65,000 hashes on average. Bitcoin uses difficulty 20+, requiring trillions of attempts!
Rewriting history requires re-mining every block, which is computationally prohibitive.
Part 3: Transactions & Economics
Transaction Model
Each block contains multiple transactions:
class Transaction {
std::string sender;
std::string receiver;
double amount;
time_t timestamp;
};
Balance Tracking
Balances aren’t stored directly—they’re calculated by scanning the entire chain:
double Blockchain::getBalance(std::string address) {
double balance = 0;
for (const Block& block : chain) {
for (const Transaction& tx : block.transactions) {
if (tx.sender == address) balance -= tx.amount;
if (tx.receiver == address) balance += tx.amount;
}
}
return balance;
}
Mining Rewards
Where does money come from? Miners create it!
Every block includes a special “coinbase” transaction:
From: SYSTEM
To: MINER
Amount: 50
This is how Bitcoin’s 21 million coins are gradually minted—through mining rewards that halve every 210,000 blocks.
Part 4: Distributed Networking
This is where it gets really interesting.
TCP Sockets: The Foundation
Server (listening for connections):
// Create socket
int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
// Bind to port
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
bind(serverSocket, (sockaddr*)&addr, sizeof(addr));
// Listen for connections
listen(serverSocket, 5);
// Accept incoming connections
int clientSocket = accept(serverSocket, nullptr, nullptr);
Client (connecting to a peer):
int peerSocket = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in peerAddr;
peerAddr.sin_family = AF_INET;
peerAddr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &peerAddr.sin_addr);
connect(peerSocket, (sockaddr*)&peerAddr, sizeof(peerAddr));
Sockets are like phone calls—one side listens, the other dials, and once connected, they can exchange messages.
Multi-Threading: Handling Multiple Peers
Each node needs to:
- Listen for incoming connections (blocking)
- Handle multiple connected peers simultaneously
- Send/receive messages without freezing
Solution: Spawn a thread for each task:
// Background listener thread
std::thread listenerThread([this]() {
while (running) {
int peerSocket = accept(serverSocket, nullptr, nullptr);
// Spawn handler for this peer
std::thread(&Node::handlePeer, this, peerSocket).detach();
}
});
Multiple threads accessing the blockchain simultaneously!
void Node::mineAndBroadcast(std::vector<Transaction> txs) {
chainMutex.lock(); // Lock before modifying
blockchain.addBlock(txs);
Block& newBlock = blockchain.getChain().back();
chainMutex.unlock(); // Unlock
// Broadcast to all peers (no lock needed)
std::string msg = createBlockMessage(newBlock);
broadcastMessage(msg);
}
Hold locks for minimum time. Broadcasting is slow (network I/O), so unlock before broadcasting!
Message Protocol
Nodes communicate using JSON messages:
// Request chain
{"type":"GET_CHAIN"}
// Send chain
{"type":"CHAIN","data":[{block1}, {block2}, ...]}
// Broadcast new block
{"type":"NEW_BLOCK","data":{block}}
// Query chain length
{"type":"GET_LENGTH"}
{"type":"LENGTH","value":5}
Why JSON? Human-readable, easy to debug, widely supported. Production systems use binary protocols (Protobuf, etc.) for efficiency.
Chain Synchronization
When nodes connect, they need to agree on the blockchain:
Node A (3 blocks) ←→ Node B (5 blocks)
1. A asks B: "How long is your chain?"
B responds: "5 blocks"
2. A realizes B has a longer chain
A requests: "Send me your full chain"
3. B sends all 5 blocks
4. A validates the chain:
- All hashes correct?
- All links valid?
- Longer than my chain?
5. If valid, A adopts B's chain
The longest valid chain wins. This is how distributed consensus works!
Part 5: Technical Challenges & Solutions
Challenge 1: Blockchain Forks
Problem: Two miners find blocks simultaneously
┌─ Block 2A (Node 1)
Genesis ─ Block 1 ─┤
└─ Block 2B (Node 2)
Solution: Longest chain rule. When Node 1 mines Block 3A, Node 2 abandons 2B and adopts the 2A→3A chain.
Challenge 2: Race Conditions
Problem: Thread A reading chain while Thread B modifies it
Solution: Mutexes
std::mutex chainMutex;
// Writer
chainMutex.lock();
blockchain.addBlock(newBlock);
chainMutex.unlock();
// Reader
chainMutex.lock();
int length = blockchain.getChainLength();
chainMutex.unlock();
Challenge 3: JSON Parsing
Problem: Parsing nested JSON by hand in C++
{"type":"CHAIN","data":[{"tx":[{...},{...}]},{"tx":[{...}]}]}
Solution: Brace counting algorithm
int braceCount = 0;
for (size_t i = 0; i < json.length(); i++) {
if (json[i] == '{') braceCount++;
if (json[i] == '}') {
braceCount--;
if (braceCount == 0) {
// Found complete JSON object
extractBlock(startPos, i);
}
}
}
Conclusion
Building a blockchain from scratch demystified how cryptocurrencies actually work. It’s not magic—it’s clever application of cryptography, distributed systems, and economic incentives.
Key takeaways:
- Blockchains are just tamper-evident linked lists
- Proof-of-work makes rewriting history expensive
- Distributed consensus requires careful protocol design
- Systems programming teaches you how computers really work
The full source code is available on GitHub.