-
-
Save ajtowns/ec27774113e5d1214b99281f6b7549c4 to your computer and use it in GitHub Desktop.
sim address relay
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import random | |
from math import log1p | |
from collections import defaultdict | |
import gc | |
import sys | |
def rand_interval(avg): | |
return avg * -log1p(-random.random()) | |
def stats(nums): | |
avg = sum(nums)/len(nums) | |
stddev2 = sum( (n-avg)**2 for n in nums ) / (len(nums)-1) | |
return avg, stddev2**0.5 | |
class Node: | |
@classmethod | |
def set_params(cls, **d): | |
defaults = dict( | |
n_outbound = 8, | |
n_black = 2, | |
max_inbound = 117, | |
n_addr_relay = 2, | |
limit_hops = 10000, | |
limit_time = 600, # don't relay addr older than 10m | |
avg_time = 30, # poisson 30s interval between sending addr | |
) | |
for k in d: | |
assert k in defaults, "%s not in defaults" % (k) | |
for k,v in defaults.items(): | |
if k in d: | |
v = d[k] | |
setattr(cls, k, v) | |
def __init__(self, type): | |
self.peers_o = [] | |
self.peers_b = [] | |
self.peers_i = [] | |
self.type = type | |
def enough_outbounds(self): | |
return len(self.peers_o) >= self.n_outbound | |
def enough_black(self): | |
return len(self.peers_b) >= self.n_black | |
def connect(self, other, black=False): | |
if black: | |
assert not self.enough_black() | |
else: | |
assert not self.enough_outbounds() | |
if self is other: return False | |
if other in self.peers_i: return False | |
if other in self.peers_o: return False | |
if other in self.peers_b: return False | |
if len(other.peers_i) >= self.max_inbound: return False | |
assert self not in other.peers_i | |
assert self not in other.peers_o | |
assert self not in other.peers_b | |
if black: | |
self.peers_b.append(other) | |
else: | |
self.peers_o.append(other) | |
other.peers_i.append(self) | |
return True | |
def rand_peers(self, cnt): | |
o = len(self.peers_o) | |
i = len(self.peers_i) | |
cnt = min(cnt, o+i) | |
samp = random.sample(range(o+i), cnt) | |
res = [] | |
for n in samp: | |
if n < o: | |
res.append(self.peers_o[n]) | |
else: | |
res.append(self.peers_i[n-o]) | |
return res | |
@classmethod | |
def _relay_addr(cls, addr, start): | |
process = [(start,0)] | |
seen_id = set() | |
hop = 0 | |
cnts = defaultdict(int) | |
while process and hop < cls.limit_hops: | |
hop += 1 | |
next_process = [] | |
for p,time in process: | |
if id(p) in seen_id: continue | |
seen_id.add(id(p)) | |
cnts[p.type] += 1 | |
if time > cls.limit_time: continue | |
ntime = time + rand_interval(cls.avg_time) | |
r = p.rand_peers(cls.n_addr_relay) | |
for n in r: | |
if p in n.peers_b: continue | |
next_process.append((n,ntime)) | |
process = next_process | |
return cnts | |
def relay_addr(self, addr): | |
return self._relay_addr(addr, self) | |
def run_sim(listen=10000, priv=50000, n_addr=100): | |
nodes = [Node('l') for n in range(listen)] | |
privnodes = [Node('p') for n in range(priv)] | |
for n in nodes + privnodes: | |
while not n.enough_outbounds(): | |
n2 = random.choice(nodes) | |
n.connect(n2) | |
while not n.enough_black(): | |
n2 = random.choice(nodes) | |
n.connect(n2, black=True) | |
res = {'l': [], 'a': []} | |
for addr in range(n_addr): | |
n = random.choice(nodes + privnodes) | |
cnt = n.relay_addr(addr) | |
rl = cnt['l'] / listen | |
ra = (cnt['l'] + cnt['p']) / (listen + priv) | |
res['l'].append(rl) | |
res['a'].append(ra) | |
avgl, devl = stats(res["l"]) | |
avga, deva = stats(res["a"]) | |
print("Conn: %d+%d Spread: %d Limit: %d/%d Avg/dev: %.1f%%/%.1f%% %.1f%%/%.1f%%" % (Node.n_outbound, Node.n_black, Node.n_addr_relay, Node.limit_hops, Node.limit_time, avgl*100.0, devl*100.0, avga*100.0, deva*100.0)) | |
del nodes | |
del privnodes | |
gc.collect() | |
#Node.set_params(n_outbound=6, n_black=2) | |
#run_sim() | |
#Node.set_params(n_outbound=6, n_black=2, limit_hops=20, limit_time=600*10000) | |
#run_sim() | |
def check(): | |
if len(sys.argv) == 4: | |
Node.set_params( | |
n_outbound = int(sys.argv[1]), | |
n_black = int(sys.argv[2]), | |
n_addr_relay = int(sys.argv[3]), | |
) | |
run_sim(n_addr=1000) | |
return | |
elif len(sys.argv) != 1: | |
print("Args: none or outbount blackhold spread") | |
return | |
#now | |
Node.set_params(n_outbound=8, n_black=2) | |
run_sim() | |
#with 21528 | |
Node.set_params(n_outbound=8, n_black=0) | |
run_sim() | |
#more block-relay-only without 21528 | |
Node.set_params(n_outbound=8, n_black=5) | |
run_sim() | |
#more block-relay-only without 21528 but 3-peers-addr-relay | |
Node.set_params(n_outbound=8, n_black=5, n_addr_relay=3) | |
run_sim() | |
#with 21528 and 3-peers-addr-relay | |
Node.set_params(n_outbound=8, n_black=0, n_addr_relay=3) | |
run_sim() | |
if __name__ == "__main__": | |
check() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment