import json import os import statistics import subprocess import time from collections import defaultdict from http.server import BaseHTTPRequestHandler, HTTPServer from threading import Thread, Lock def make_handler(timestamps, connectivity, num_nodes, callback): class TimestampHandler(BaseHTTPRequestHandler, object): def __init__(self, request, client_address, server): self.timestamps = timestamps super().__init__(request, client_address, server) def _send_response(self): self.send_response(200) self.send_header("Content-type", "text/html") self.end_headers() def do_GET(self): print("Get request", str(self.path)) self._send_response() def do_POST(self): content_length = int(self.headers["Content-Length"]) transaction_hash = self.rfile.read(content_length) t = time.time() self.timestamps.append(t) print(f"POST {transaction_hash.hex()} at {t}") print(f"Number of notifications: {len(self.timestamps)}/{num_nodes}") if len(self.timestamps) == num_nodes: print("Number of notifications is equal to the number of nodes; closing server and calling callback") self.server.server_close() callback(self.timestamps) self._send_response() def do_PUT(self): content_length = int(self.headers["Content-Length"]) peer_info_bytes = self.rfile.read(content_length) peer_info = json.loads(peer_info_bytes.decode(encoding="ascii")) connectivity[peer_info["node"]] = peer_info["peerlist"] self._send_response() return TimestampHandler def print_stats(timestamps, connectivity): print("-----------------------------------------") print(f"Received notification from {len(timestamps)} nodes") first = timestamps[0] last = timestamps[-1] print("First timestamp at", first) print("Last timestamp at", last) print("Difference:", (last - first) * 1000, "ms") print() print("Median time it took:", statistics.median([ts - first for ts in timestamps[1:]]) * 1000, "ms") print("Average time it took:", statistics.mean([ts - first for ts in timestamps[1:]]) * 1000, "ms") print("Connectivity:") print(json.dumps(connectivity)) def graph(timestamps): pass def start_node(miners, max_peers, interface, seed, delay): return subprocess.Popen(["python3.6", "-m", "aucoin", "--miners", str(miners), "--max-peers", str(max_peers), "--interface", interface, "--seed", seed, # 192.0.2.0 doesn't exist according to RFC "--notify-url", "http://localhost:8080", "--delay", str(delay), "--no-catch-up", "-v", "--clean"], env=dict(os.environ, HOME=f"~/.aucoin-test/{interface}")) def run_experiment(num_nodes): results = defaultdict(dict) # results[delay][max_peers] = timestamps continue_lock = Lock() for delay in [1]: for max_peers in (4,): continue_lock.acquire() print("==================================================================") print(f"Running experiment with {num_nodes} nodes at {delay}ms delay and max_peers={max_peers}") print("Please start seed node manually using:") print("VVV") print(f"python3.6 -m aucoin --miners=0 --max-peers={max_peers} --interface=127.0.0.1 --seed=192.0.2.0 --notify-url http://localhost:8080 --delay={delay} --no-catch-up -v") print("^^^") # input("Press any key when done to continue") #print("Waiting 1s for the seed node to fail connecting to non-existent seed..") #time.sleep(1) print("Continuing..") print("Starting other nodes..") nodes = [] for n in range(2, num_nodes+2): # start at 127.0.0.2 node = start_node(miners=0, max_peers=max_peers, interface=f"127.0.0.{n}", seed="127.0.0.1", delay=delay) nodes.append(node) print("Sleeping 10s to allow establishing connections..") time.sleep(5) print("Continuing..") def all_nodes_received_transaction_callback(timestamps): print("Killing all nodes..") for node in nodes: node.kill() time.sleep(1) print("VVV") print("Please Ctrl+C the seed node") print("^^^") input("Press any key when done..") print("Saving and printing stats..") results[delay][max_peers] = timestamps print_stats(timestamps, connectivity) print("=== RESULTS ===") print(json.dumps(results)) print("===============") continue_lock.release() print("Starting web server in other thread") timestamps = [] connectivity = {} http_server = HTTPServer(("0.0.0.0", 8080), make_handler(timestamps, connectivity, num_nodes + 1, all_nodes_received_transaction_callback)) # num_nodes + 1 accounts for seed node http_thread = Thread(target=http_server.serve_forever) http_thread.start() print("Server started on port 8080") print("Waiting 2s for the webserver to start up..") time.sleep(2) print("Continuing..") print("Please send a transaction from the seed node, e.g.:") print("VVV") print("send aabb 10") print("^^^") print("Waiting for all peers to receive transaction..") for node in nodes: node.wait() # The flow continues in all_nodes_received_transaction_callback() def plot(): times = [962, 843, 1329, 1142, 1078, 1223, 1406, 1492] worst = [1879, 1979, 2039, 2149, 2185, 2655, 2304, 3190] nodes = [10, 20, 30, 50, 75, 100, 125, 150] import matplotlib.pyplot as plt import matplotlib.patches as mpatches plt.plot(nodes, times, 'r-x', nodes, worst, 'b-x') plt.xlabel('number of peers') plt.ylabel('time in milliseconds from transaction was sent') red = mpatches.Patch(color='red', label='median') green = mpatches.Patch(color='blue', label='last record') plt.legend(handles=[green, red]) plt.show() if __name__ == '__main__': run_experiment(num_nodes=70) # plot()