In the previous post, we introduced a single miner. Now let's set up 3 nodes that mine and broadcast to each other.
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.
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.