183 lines
6.7 KiB
Python
183 lines
6.7 KiB
Python
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()
|