In the previous post, we introduced a single miner. Now let's set up 3 nodes that mine and broadcast to each other.

Context

Let's start by sketching out what each node would do. In mining mode, the node tries 1,000 nonce values at a time and extends the blockchain if a solution is found. In listening mode, the node listens to the network and replaces its blockchain if the broadcasted blockchain is valid and longer than the existing blockchain. To keep things simple, the node would alternate between the two modes instead of running both in parallel.

while True:
    try:
        # Listen for incoming messages.
        #     If message received, check blockchain received is valid and longer      
        #     than existing blockchain.
        #         If true, replace existing blockchain.
        #         Otherwise, continue listening.
        #     Otherwise, switch to mining mode.
    except:
        # Run proof-of-work.
        #     If not solved after 1000 nonce values, switch to listening mode.
        #     Otherwise, update blockchain and broadcast to network.

Broadcasters

Since most of our focus so far has been on mining, let's switch gears briefly to set up nodes that broadcast to each other. To ensure our nodes are set up correctly, we create a .env file with the IP address by running the following command in a terminal window.

echo 'NODE_IP='"$(ipconfig getifaddr en0)" > .env

Next we load the IP address as an environment variable, and enumerate all the ports to be used.

import dotenv
import os

dotenv.load_dotenv()

NODE_IP = os.getenv("NODE_IP")
NODE_PORTS = [7000, 8000, 9000]

We introduce a helper function to bind the network socket to the IP address-port pair.

import socket

def bind_socket(ip_address: str, port: int) -> socket.socket:
    """ """
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind((ip_address, port))
    return sock

Let's wrap the socket in a class and create functions to (i) initialize the node, and (ii) broadcast to other nodes. The node class also stores the blockchain state - we'll use this in the next section.

import dataclasses

@dataclasses.dataclass
class Node:
    """ """

    port: int
    sock: socket.socket
    blockchain: bytes
    block_counter: int

def init_node(
    port: int, blockchain: bytes = b"", block_counter: int = 0
) -> Node:
    """ """
    assert NODE_IP is not None
    sock = bind_socket(NODE_IP, port)

    return Node(
        port=port,
        sock=sock,
        blockchain=blockchain,
        block_counter=block_counter,
    )

def broadcast(node: Node, message: bytes):
    """ """
    for node_port in NODE_PORTS:
        if node_port == node.port:
            continue

        node.sock.sendto(message, (NODE_IP, node_port))

In the placeholder version, we set up the nodes to broadcast a simple message to the network and then sleep for a random time.